cplus-plus.ru logo
Мы переехали на cplus-plus.ru
Главная страница В закладкиО сайтеКарта сайта
Хостинг от uCoz
Добавить в закладки

Меню сайта

Полезные ссылки

Наша рассылка
Подписаться на рассылку
"C++ : cplus-plus.ru :
Рассылка статей C++"


Друзья сайта
alsproject.ru Выбор выходного разделительного конденсатора

Приветствую Вас, Гость · rss 18-Апр-2024, 11:27
Главная » 2010 » Декабрь » 22 » Скриптовая подсистема на базе интерпретатора CInt
12:42
Скриптовая подсистема на базе интерпретатора CInt

Скриптовая подсистема на базе интерпретатора CInt
To be compiled or to be interpreted

Программная система сама по себе сущность неоднородная и многогранная. Его можно рассматривать с разных сторон и преследовать различные цели. Любое такое исследование призвано описать новую или возможно уже изученную сторону той или иной проблемы, взаимосвязи. А полученные результаты помогают глубже понять прикладную задачу и методы её решения.

Ниже описан механизм расширения функциональности приложения посредством подключение интепретатора CInt (интепретатор С/С++) и представлен метод унифицированного выполнения скриптов как в интерпретируемой среде, так и виде скомпилированного кода.

Структурная модель приложения

Приступая к изучению любой системы, в первую очередь нужно определить её структурные границы. С одной стороны это позволяет избежать рассмотрения неоправданно обобщенных конструкций, с другой, не погрязнуть в деталях и низкоуровневых нюансах, не существенных на рассматриваемом уровне. Для каждой системы можно выделить три основных элемента. Первый – среда, в которой функционирует система, второй – сама рассматриваемая система и третий – множество вложенных структур, специализирующих поведение рассматриваемой сущности.

Это один из равновозможных вариантов разбиения, но именно на его основании будет построено изложение.

Если перенести такое разделение на информационные системы, то основным связывающим звеном всех компонентов будут каналы информации, по которым происходит передача сигналов/событий/данных в обоих направлениях. Операционная система-приложение-вводимые пользователем команды; браузер-HTML страница-код JavaScript: вот примеры таких организационных связей.

Следующим шагом уточним рассматриваемое множество до уровня подмножества программных систем. Такое множество ограничено снизу операционной средой (операционной системой), которая непосредственно взаимодействует с аппаратной частью. Сверху множество не ограничено, т.к. вложенность программных систем друг в друга может быть бесконечной (счётной).
Завершая цикл детализации, определим следующую схему программной системы Рис.1.


С одной стороны любая система функционирует в некотором окружении (Environment, Application), встраивается в него (embedding). С другой стороны, существует возможность настраивать её поведение посредством внешних (Application, Configurations) механизмов (extending).


На данном уровне не делается никаких предположений о технологиях создания компонентов и способах функционирования. Это могут быть связки: операционная система-приложение-база данных, web сервер-php скрипт-конфигурационный файл, приложение-динамический модуль-параметры реестра.

Прикладная модель приложения

Определив структурные части, можно проанализировать особенности, возникающие на границе её составных частей. Логично, что среда функционирования должна предоставлять широкий, но стандартизированный интерфейс т. к. она предназначена для встраивания большого количества различных приложений. Следовательно, каждое приложение, созданное для одной и той же среды должно выполнять стандартный контракт по взаимодействию с ней. Использование этого правила приводит к появлению так называемых frameworks, повышающих повторное использование кода, а также скорость разработки (далее framework, фреймворк, каркас и движок используются как синонимы).

С другой стороны, процесс подстройки\настройки\конфигурирования может производиться большим количеством способов: параметрами командной строки, с помощью конфигурационных файлов, интерактивного пользовательского ввода, плагинов, скриптов и пр. Здесь также необходимо выполнять контракт по взаимодействию, и в дополнение каждый из механизмов расширения обладает различными характеристиками обобщённости, универсальности, расширяемости и пр. Что является критериями при выборе того или иного инструмента.

На основании сделанных выше заключений можно выделить следующие элементы приложения Рис.2.


Соответственно, данная схема также не накладывает никаких ограничений на реализацию каждой из частей. Framework может быть написан на Python, Application реализован как DLL на C++, а Configurations настраивает поведение системы с помощью xml файла.

Скриптовая подсистема

Как было показано выше, специализация поведения приложения может быть осуществлена большим количеством способов. Здесь же мы рассмотрим один из вариантов – использование скриптов как механизм расширения функциональности приложения.

Каково же основное назначение скриптовой подсистемы? Это предоставление ортогонального интерфейса времени выполнения к функциональному ядру системы. Интерфейса, который глубже «проникает» в систему по сравнению с любыми операциями, доступными с помощью стандартных механизмов поведения приложения. Большинство пакетов 2D/3D моделирования, математических пакетов и финансовых инструментов содержат подобную функциональность.

В первую очередь сформулируем требования к разрабатываемой подсистеме.

Создание эффективных систем требует наличие возможности для повторного использования кода, которое приводит нас к следующей схеме на Рис.3.


Тезис 1: Скриптовый код должен быть максимально адаптирован для повторного использования.

Различные прикладные задачи могут потребовать от нас разные формы конечной сборки исходных кодов в исполнимые\библиотечные модули Рис.4.


Рассмотрим отдельно каждую из них.

Первый вариант a), характерен для случаев, когда время выполнения скриптов критично, и они собираются вместе с базовым приложением в единый модуль. В таких ситуациях часто для написания скриптов используется тот же язык программирования, что и для приложения.
Для варианта b) понятия «приложение» и «framework» не разделяются. Это могут быть специализированные разработки, для которых обобщенность и повторное использование жертвуются в угоду скорости и эффективности.
Вариант c) – пример монолитной системы «всё включено».
И вариант d) – немного «идеализированная» ситуация. Такая структура является в некотором роде эталоном, так как она отражает сбалансированный вид программного продукта. И как это обычно бывает – такой результат достаточно труднодостижим.

Тезис 2: Реализация скриптовой подсистемы должна позволять эффективно реализовывать четыре описанные интеграционные схемы.

Не все скриптовые языки и системы могут быть доступны на всех целевых платформах. Отдельный скриптовый язык (подсистема) может быть менее эффективным по сравнению с другим языком (подсистемой) для решения конкретных задач при всех прочих равных условиях. На Рис.5 изображена схематичная таблица покрытия платформ. Используя подобную таблицу, на этапе анализа среды функционирования, можно выбрать скриптовый язык с максимальным покрытием и эффективной работой на целевом подмножестве платформ и задач.



Тезис 3: Скриптовая подсистема должна обеспечивать возможность параметризации скриптовым языком.

Теперь можно сформулировать конечные требования для подсистемы: разрабатываемый дизайн должен обладать высокой степенью общности, пригодной для повторного использования, легко интегрируемым, гибким, иметь возможность параметризации скриптовым языком.

C++ + CInt

Этот подраздел посвящён описанию реально разработанной и используемой скриптовой подсистемы, которая является частью каркаса (движка) z3d.

Система реализована в следующем базисе: framework – C++, application – C++, configurations – CInt.

Использование интерпретации имеет много преимуществ: это отсутствие необходимости изменения бинарного кода, широкие алгоритмические возможности, простота использования, гибкость, нестрогая типизация и пр. Но скорость выполнения скриптов является тем минусом, который ограничивает спектр её применения.
Различные системы по-разному «смягчают» этот фактор: интерпретатор Python создаёт pyc-файлы, .NET платформа исполняет byte-код, Facebook «перегоняет» php-скрипты на С++.

Ключевым свойством разработанной системы является наличие общей схемы, которая позволяет использовать как интерпретацию скриптов, так и подключать их на этапе компиляции и «вкомпиливать» в бинарную сборку модуля.

Скриптовый язык

В качестве скриптового языка был выбран CInt. Это интерпретатор языков C/C++. Для включения CInt в проект можно использовать скомпилированную библиотеку с официального сайта. Однако рекомендуется пересобрать её из исходных кодов с помощью установленного компилятора, с возможностью контроля параметров компиляции и сборки.
Последовательность действий для сборки библиотеки под MS Windows с помощью cygwin и msvc9 (msvc8, msvc7) описана здесь.

Архитектура подсистемы

Давайте рассмотрим дизайн скриптовой подсистемы и его составные части.
В первую очередь, вся скриптовая подсистема реализована в виде набора заголовочных файлов. Пользователь библиотеки может включать их как в исполнимый модуль, так и как часть динамической или статической библиотек.

На Рис.6 отражена диаграмма ключевых компонентов.


Класс z3d::script::cint::language инкапсулирует представление и поведение конкретного скриптового языка CInt (инициализация, деинициализация, обработка создания контекста и пр.). Класс z3d::script::cint::context – отражает логическую замкнутую область-«песочницу», внутри которой работает интерпретатор (вызовы функций, вычисления выражений).

Шаблонные классы z3d::script::language и z3d::script::context реализуют общую для всех скриптовых подсистем функциональность (агрегацию контекстов, управление временем жизни).
Такая организация позволяет нам удовлетворить Тезис 3 и части b) и d) Тезиса 2.

Функциональность, реализованная во framework, может быть двух типов:

  • интерпретируемый код (прототипы и реализация скриптовых функций общего назначения)

  • прототипы функций-шлюзов для вызова из скриптового кода. Другими
    словами точки входа в скомпилированный код framework из скриптов
    приложения

На Рис. 7 изображена диаграмма размещения исходных кодов скриптов.





Приложение: stdafx.h, stdafx.cpp

Скрипты приложения: scr.h – файл, содержащий общие определения для скриптов и приложения; scr.inl – файл, включающий в себя все скрипты приложения для «вкомпиливания» скриптов; main.inl, config.inl – скриптовый функционал приложения.
Скрипты framework: engine.h – прототипы функций-шлюзов для доступа к функциональности framework; common.h, vfs.h, pref.h, ui.h – скриптовая функциональность каркаса; common.inl, ui.inl – реализация скриптовой функциональности framework.

Эта схема позволяет хранить и использовать скрипты как на уровне framework, так и на уровне приложения и использовать их вместе в контексте приложения, удовлетворяя Тезис 1.

И последняя по списку, но первая по значению, это константа препроцессора Z3D_SCRIPT_CINT_INTERPRETER.
Если определить эту константу в программном коде, то после компиляции модуля, все вызовы методов объекта z3d::script::cint::context буду направляться в интерпретатор CInt.
Если же эта константа не определена, то скриптовый код будет откомпилирован, а все скриптовые вызовы будут находиться в границах скомпилированного модуля. В этом случае программный код, написанный на С++, и скриптовый С/С++ код будут скомпилированы и собраны вместе.

Такое поведение возможно, во-первых, благодаря определённому механизму включения скриптов в проект Рис.7, во-вторых, благодаря возможностям секции экспорта PE (Portable Executable).
Тут требуется небольшое пояснение. Вызовы скриптов осуществляются посредством строковых выражений времени выполнения приложения, подаваемых на вход интерпретатора. В случае же «вкомпиленных» скриптов строковая семантика теряется после компиляции. Вот тут и приходит на помощь возможность поместить функцию в секцию экспорта. Такой механизм работает не только для DLL, но и для EXE модулей. Таким образом, после сборки модуля вы легко можете восстановить строковую семантику функции с помощью GetProcAddress.

Вот небольшая иллюстрация:

template< typename R, typename P1 >
R call( std::string const& func, P1 p1 )
{
#ifdef Z3D_SCRIPT_CINT_INTERPRETER
std::stringstream ss;
ss << func << '(' << z3d::type_name::param_( p1 ) << ");";
return ( result< boost::is_float< R >::value >::convert< R >( G__calc( ss.str().c_str() ) ) );
#else
return ( dict.get<R(*)( P1 )>( func )( p1 ) );
#endif
}

где функция CInt — G_calc, отправляет строку в интерпретатор, а метод dict::get возвращает указатель на функцию с именем указанным в качестве параметра func, осуществляя поиск функции по имени в экспортной секции модуля.
Это позволяет покрыть части a) и c) Тезиса 2.

Результаты

Данный дизайн позволил объединить гибкость интерпретируемой системы и производительность скомпилированной сборки. Как и раньше можно включать интерпретатор скриптов в финальную версию программы, можно использовать его на этапе разработки и прототипирования, для автоматизации тестирования, рассматривать «компиляцию скриптов» как их верификатор и т.д
Ниже приведён небольшой пример использования подсистемы

// create CInt language objects
z3d::script::cint::language::language_ptr_t l = z3d::script::cint::language::create( env.get_log() );

// create context (sandbox)
z3d::script::cint::context::context_ptr_t c = l->make_context();

// load script file into context
c->load( "main.inl" );

// call scripts
c->call< void >( "func" );
c->call< void >( "func1", const_cast<char*>("abcdef") );
c->call< void >( "func2", 1, 2.f );
c->call< void >( "func10", 0, 'A', 1.f, 1, 'B', 2.f, 3, 'C', 3.f, true );


Определяя или наобор удаляя константу Z3D_SCRIPT_CINT_INTERPRETER можно получать как выполение скриптов через интерпретатор, так и выполение их из бинарного модуля.

Необходимо также отметить ряд компромиссов, на которые пришлось пойти в процессе реализации подсистемы:

  • вычисление выражений будет работать в интерпретируемой сборке, но
    будет невозможным во «вкомилированном» базисе (вычисление выражения типа
    «2+2*2»)

  • для реализации вызова скриптовых функций, была разработана система
    сопоставления сигнатуры вызова функции и соответствующего строкового
    представления, увеличивающая время компиляции за счёт применения методов
    метапрограммирования. f(1, 1.f) -> "f((int)1, (float)1.f);” (здесь рассмотрены детали реализации)
В планах по развитию скриптовой подсистемы можно отметить интеграцию в качестве скриптовых языков Ch, Python, Lua. Последние два языка не позволят «вкомпиливать» скрипты в код, но не все задачи требуют подобного поведения. Ещё одна задача — реализация «прозрачной» передачи объектов, созданных внутри скриптового кода на уровень приложения и наоборот.

Упомянутые здесь исходные коды можно получить, скачав z3d sdk. После установки, в папке <z3d-root>/sam/win можно найти проект, демонстрирующий использование скриптовой подсистемы (msvc9). Для работы с исходным кодом, потребуются библиотеки Boost, Loki

Источник: http://habrahabr.ru
Автор: VladislavKurmaz

Источник: Скриптовая подсистема на базе интерпретатора CInt
Категория: Новости | Просмотров: 820 | Добавил: FazaNaka | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]