Russian Qt Forum
Сентябрь 28, 2024, 12:35 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Использование одного скрипта из под другого в QtScript  (Прочитано 9357 раз)
iRQSX
Гость
« : Июнь 15, 2011, 13:12 »

Начну немного из далека, что бы немного разъяснить суть. Хочу сделать прогу-оболочку для БД и часть программы реализовать в скриптах. Задумка примерно такая: подготовить часть программы на cpp, в которой реализовать все функции работы с бд, а всю логику и GUI вынести в QtScript. Получается много модулей скриптов, у каждого из которых есть свой ui и один скрипт должен мочь использовать функции из другого, а еще уметь передавать параметры функции. Примерно так
Script1.js
Код
Javascript
function run(a, b)
{
   return a+b;
}
 
Script1.js
Код
Javascript
function run(){
   ui.show();
   run(1,13);
}
 
Накидал такой класс:
scriptengine.h
Код
C++ (Qt)
class EKERNELSHARED_EXPORT scriptengine : public QObject
{
   Q_OBJECT
private:
   static DBEngine m_dbe;
   usermodules m_um;
   //QScriptEngine m_se;
   static QString m_configpath;
   static QScriptValue load(QScriptContext *context, QScriptEngine *engine);
public:
   explicit scriptengine(QObject *parent = 0);
public slots:
   void setConfigPath(const QString &);
   QString configPath();
   static void loadModule(const QString &moduleName, const QString &functionName = QString("run"),
                   const QScriptValueList arguments= QScriptValueList());
};
scriptengine.cpp
Код
C++ (Qt)
scriptengine::scriptengine(QObject *parent) :
   QObject(parent){
   configs cfg;
   m_configpath = cfg.readSetting("usermodules","ModulesPath").toString();
}
 
void scriptengine::loadModule(const QString &moduleName, const QString &functionName,
                             const QScriptValueList arguments){
   QWidget *dialog;
   QUiLoader loader;
   QScriptEngine *m_se=new QScriptEngine();
   QFile file(m_configpath+"/"+moduleName+".ui");
   if (!file.open(QFile::ReadOnly)){
       return;
   }
   dialog = loader.load(&file);
   file.close();
   file.setFileName(m_configpath+"/"+moduleName+".js");
   if (!file.open(QFile::ReadOnly)){
       return;
   }
   QTextStream filets(&file);
   QScriptEngineDebugger debugger;
   //    debugger.attachTo(m_se);
   //    debugger.action(QScriptEngineDebugger::InterruptAction)->trigger();
   m_se->evaluate(filets.readAll(),moduleName);
   m_se->globalObject().setProperty("ui",m_se->newQObject(dialog));
   m_se->globalObject().setProperty("db",m_se->newQObject(&m_dbe));
   m_se->globalObject().setProperty ( "load", m_se->newFunction (scriptengine::load));
   file.close();
   QScriptValue fun = m_se->globalObject().property(functionName);
   if(fun.isFunction()){
       qDebug()<< fun.call(QScriptValue(), QScriptValueList()<<arguments).toInteger();
   }
   else{
       qDebug()<<"undefined function";
   }
}
 
void scriptengine::setConfigPath(const QString &configPath){
   m_configpath = configPath;
}
 
QString scriptengine::configPath(){
   return m_configpath;
}
 
QScriptValue scriptengine::load(QScriptContext *context, QScriptEngine *engine){
   QScriptValueList args;
   for (int i=2;i<context->argumentCount();i++){
       args<<context->argument(i);
       qDebug()<<context->argument(i).toString();
   }
   loadModule(context->argument(0).toString(),context->argument(1).toString(),args);
 
}
 
Скрипты получились такие
Script1.js
Код
Javascript
function run(){
   ui.show();
   load("Script2","run1",1,13);
}
Script2.js
Код
Java
function run1(a, b)
{
   return a+b;
}
Но все это дело в runtime пдает, в консоли приложения вижу сообщение
Код:
QScriptValue::call() failed: cannot call function with argument created in a different engine
0
ASSERT: "!vv->engine || vv->engine == this" in file c:\ndk_buildrepos\qt-desktop\src\script\api\/qscriptengine_p.h, line 656
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.

Кто нибудь может подсказать как и что поправить, или вообще как можно организовать все это дело по другому?
В идеале бы канечно получить что то вида
Код
Javascript
import Script1 as sc
function run(){
   ui.show();
   sc.run1(1,4);
}
По сути, даже надо не то что бы использовать отдельные функции, а подгрузить скрипт и перезать ему параметры.
Можно бы воспользоваться наработками из темы Иммитация include в QtScript, но тогда проблема в том, что нам нужно будет обеспечить уникальность имен функций и объектов (например таких как ui), а это очень проблематично...

ЗЫ. Прошу не пинать за этот говнокод, многие вещи до меня еще не доходят...
« Последнее редактирование: Июнь 15, 2011, 13:28 от iRQSX » Записан
asvil
Гость
« Ответ #1 : Июнь 15, 2011, 13:44 »

Позаимствовано из qtscriptgenerator:

Просто cpp функция, которая выполняет ecmascript:
Код:
bool loadFile(const QString& fileName, QScriptEngine *engine, QScriptValue& retValue)
{
  QVariantMap loadedFiles = engine->property("_pg_loaded_files").value<QVariantMap>();

  QString localFileName(fileName);
  QFileInfo fileInfo(localFileName);

  QString absoluteFileName = fileInfo.absoluteFilePath();
  QString absolutePath = fileInfo.absolutePath();
  QString canonicalFileName = fileInfo.canonicalFilePath();
  if (loadedFiles.contains(canonicalFileName))
    if (loadedFiles.value(canonicalFileName).toDateTime() == fileInfo.lastModified()) {
      return true;
    }

  loadedFiles.insert(canonicalFileName, fileInfo.lastModified());
  engine->setProperty("_pg_loaded_files", loadedFiles);

  //QString path = fileInfo.path();

  // load the file
  QFile file(fileInfo.absoluteFilePath());
  if (file.open(QFile::ReadOnly)) {
    QTextStream stream(&file);
    QString contents = stream.readAll();
    file.close();

    int endlineIndex = contents.indexOf('\n');
    QString line = contents.left(endlineIndex);
    int lineNumber = 1;

    // strip off #!/usr/bin/env qscript line
    if (line.startsWith("#!")) {
      contents.remove(0, endlineIndex+1);
      ++lineNumber;
    }

    // set qt.script.absoluteFilePath
    QScriptValue script = engine->globalObject().property("script");
    QScriptValue oldFilePathValue = script.property("absoluteFilePath");
    QScriptValue oldPathValue = script.property("absolutePath");
    script.setProperty("absoluteFilePath", engine->toScriptValue(absoluteFileName));
    script.setProperty("absolutePath", engine->toScriptValue(absolutePath));

    retValue = engine->evaluate(contents, fileInfo.absoluteFilePath(), lineNumber);
    if (engine->hasUncaughtException()) {
      QStringList backtrace = engine->uncaughtExceptionBacktrace();
      qWarning() << QString("    %1\n%2\n\n").arg(retValue.toString()).arg(backtrace.join("\n"));
      return true;
    }

    script.setProperty("absoluteFilePath", oldFilePathValue); // if we come from includeScript(), or whereever
    script.setProperty("absolutePath", oldPathValue); // if we come from includeScript(), or whereever
  } else {
    qWarning() << QObject::tr("File %1 not found").arg(fileName);
    return false;
  }
  return true;
}

А теперь непосредственно функция import, которая будет вызываться из скрипта:

Код:

QScriptValue includeScript(QScriptContext *context, QScriptEngine *engine)
{
  QString currentFileName = engine->globalObject().property("script").property("absoluteFilePath").toString();
  QFileInfo currentFileInfo(currentFileName);

  QString path = currentFileInfo.path();
  QString importFile = context->argument(0).toString();
  QFileInfo importInfo(importFile);
  if (importInfo.isRelative()) {
    importFile =  path + "/" + importInfo.filePath();
  }
  QScriptValue retValue;
  if (!loadFile(importFile, engine, retValue))
    return context->throwError(QObject::tr("Failed to resolve include: %1").arg(importFile));

  return retValue;
}

Делаем функцию доступной из скрипт окружения:

Код:
...
engine->globalObject().setProperty("include", engine->newFunction(includeScript));
...


P.S. Занимался такой работой для postgresql. Получилось https://gitorious.org/pgbase.
Однако в qtscript прототипное наследование, что не ложиться на с++, в итоге перерасход памяти в два раза.
Кроме того, есть такое выражение: "если вы хотите встроить скриптовый язык, значит ваш хост язык говно".
Отчаянно рекомендую заниматся программированием для формочек БД только на скриптовых языках с нормальными GUI биндингами (фреймворками), like PyGTK, PySide.
Еще лучше сделать все в web морде и не городить миллиарды недоделанных 1c.
Записан
Denjs
Гость
« Ответ #2 : Июнь 15, 2011, 16:20 »

Начну немного из далека, что бы немного разъяснить суть. Хочу сделать прогу-оболочку для БД и часть программы реализовать в скриптах. Задумка примерно такая: подготовить часть программы на cpp, в которой реализовать все функции работы с бд, а всю логику и GUI вынести в QtScript. Получается много модулей скриптов, у каждого из которых есть свой ui и один скрипт должен мочь использовать функции из другого, а еще уметь передавать параметры функции.
Полагаю вам ещё может быть интересен мой проект QDroid.
Увы, я не могу сейчас сказать что механизмы интеграции фреймворка в С++ программу совершенны, но они есть.

При рассмотрении QDroid прошу обратить внимание на наличие механизмов подгрузки расширений, наличие шаблонов расширений содержащих образцы всех необходимых функций для регистрации в движке как самого класса так и конструктора класса, наличие ряда расширений (работа с сетью (tcp-server), html-GUI, разбор ini-файлов, работа с дочерними процессами, работа с stdin/out, работа с файлами и др). Отмечу, что можно погружать в скриптовое ядро фреймворка свои конструкторы (для классов "вкомпиленных" в приложение), и погружать туда объекты существующие в "хостовом" приложении.

да, там тоже много мусора сейчас, но оно работает.

Цитировать
Кроме того, есть такое выражение: "если вы хотите встроить скриптовый язык, значит ваш хост язык говно".
Минус один автору выражения. За непонимание сути )) .

Есть большая разница между компилируемой и интерпретируемой средой.
И есть острая необходимостью менять бизнес-логику приложения _здесь_и_сейчас_. Компилируемый язык не способен обеспечить достижение таких целей. Интерпретируемый язык - способен, особенно если он использует компоненты предоставляемые скомпилированной средой.

Цитировать
Позаимствовано из qtscriptgenerator
а вот за это - большое спасибо. Позаимствую немного к себе)
« Последнее редактирование: Июнь 15, 2011, 16:25 от Denjs » Записан
iRQSX
Гость
« Ответ #3 : Июнь 16, 2011, 07:15 »

Спасибо за ответы!
Denjs, посмотрел ваш проект, еще раз понял насколько я зеленый...
Не понял как это все дело использовать... Вроде как нужно добавлять в ваш проект свои классы, а потом их использовать в скриптах? Или же мы реализовываем классы в dll/so а ваша программа подтягивает их через, допустим QPluginLoader, или как то еще? Еще хотел поинтересоваться много ли времени ушло на этот код?
Филоненко Михаил, на счет 1с вы абсолютно верно заметили, задумка именно такая, и реализована она вряд ли когда нибудь будет. Над 1с работает группа программистов, каждый из которых за час напишет столько, сколько я за месяц, что уж тут говорить... Но думаю для учебных целей такая задача не так уж плоха(слава Всевышнему что этот код не будет реально использоваться в организациях). Ваш проект собрать не смог, не знаю как работать с смаке в qtcreator.  Есть вопросы по python коли уж речь зашла о нем. Я с этим языком не знаком, но вычитал что его можно и скомпилировать, а не только интерпретировать? Правда ли это, и если да то будет ли скомпилированный py исполняться быстрее?
« Последнее редактирование: Июнь 16, 2011, 08:27 от iRQSX » Записан
Denjs
Гость
« Ответ #4 : Июнь 16, 2011, 10:12 »

Denjs, посмотрел ваш проект, еще раз понял насколько я зеленый...
Не понял как это все дело использовать... Вроде как нужно добавлять в ваш проект свои классы, а потом их использовать в скриптах? Или же мы реализовываем классы в dll/so а ваша программа подтягивает их через, допустим QPluginLoader, или как то еще? Еще хотел поинтересоваться много ли времени ушло на этот код?
Использовать фреймворк можно двояко: изначальная схема - вы пишите на QtScript программу котороая начинает исполняться в среде фреймворка, и потом используя его механизмы подгружает из плагинов-расширений все необходимые классы, создает объектную структуру системы и "это все потом работает".

Потом появилась желание использовать эти наработки как скриптовое расширение для С++-нутой софтины (не хотелось мне возиться с загрузкой скриптов, работой с кодировками, захотелось пользовать уже созданные объекты из окружения среды QDroid и т.п.) - и повился libqdroid.so/.dll - т.е. к С++ программе фреймворк подключается через подключение внешней динамически подгружаемой библиотеки. Ваша программа в принципе не должна быть Qt-шной. Но что бы было все совсем красиво - конечно желательно.
В libqdroid.so/.dll есть ряд методов вида исполнить скрипт из файла, исполнить скрипт передаваемый в строке, исполнить скрипт "в контексте данных какого-либо объекта", получить ссылку на скриптовое ядро, и несколько сервисных функций. Если хотите - можно посомотреть как это делается в ./qDroid_Bot_Hive/qdroid.shell.app и ./qDroid_Bot_Hive/libqdroid.app

т.е. ваш проект в принципе ничего не обязан знать о том что творится внутри фреймворка, о его классах и прочем.
Но получив ссылку на ядро вы можете погрузить туда ваши рантаймовые объекты или зарегистрировать конструкторы ваших собственных классов (что бы работать с ними в скрипте) - ну в общем все что хотите - как с обычным скриптовым ядром созданным вами самими.

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

QPluginLoader не использую - у QDroid собственная система подключения расширений. Деталей уже не помню, но чего-то мне не хватало в QPluginLoader... то ли мне надо было регистрировать типы в системе метаданных, то ли надо было сразу регистрироватаь конструкторы, то ли там были какие-то сложности со сборкой/созданием плагинов... в общем не вышло мне с ним подружиться.
В принципе - возможностей стандартной системы метаданных Qt вполне хватает.

проект развивается примерно с ноября-декабря 2008 г.

В настоящее время есть проблемы с высвобождением памяти. Принудительно выгрузить то что создал однажды отработавший на ядре скрипт - практически невозможно. (если вы просто сделали eval() какого либо скрипта - то все определенные в нем функции у вас будут доступны в новом скрипте которому вы делаете eval() )
Т.е. если вы определили в скрипте какую-либо функцию или объект - на практике - оно продолжит свое существование до момента уничтожения самого ядра.

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

_________________________________________________________
насчет "1цэ" - есть у меня другой проект - QD4ORM.
Qt-шный ORM c cистемой метаданных, контролем структуры БД, рантаймовым "автогенератором" GUI-форм и журналов.
Без конфигуратора (т.е. метаданные класса вы описываете в его конструкторе). Предполагает интеграцию с QDroid.
Сейчас натаскан на SQLite и MySQL. ожидается MSSQL.
Увы, заопенсорсить QD4ORM полностью я пока не могу - надо выделить из текущего проекта опенсорсную часть и проприетарную, разделить опенсорсное в отдельный проект и т.п.... в общем "дело это достаточно вялотекущее" но в процессе....
« Последнее редактирование: Июнь 16, 2011, 10:42 от Denjs » Записан
asvil
Гость
« Ответ #5 : Июнь 16, 2011, 11:05 »

Цитировать
Есть большая разница между компилируемой и интерпретируемой средой.
common lisp реализованный sbcl-ом. интерпретатор-копилятор занимает 50мб и встраивается в вашу программу. Для модификации программы налету существует связка swank-slime. Вы можете подключится к программе через сеть и модифицировать ее _из_Минска_в_Москве_прямо_сейчас_. Я его не приводил как пример, так как еще  играюсь с GUI биндигами. Поддержка sql в нем есть.

Python тоже самое что c++, только не надо писать типы. На и конструктор-деструктор по-другому называются.

Записан
asvil
Гость
« Ответ #6 : Июнь 16, 2011, 11:12 »

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

стоп, там жеш gc для переменных определенных с помощью var. и оператор delete 'object.property'.
« Последнее редактирование: Июнь 16, 2011, 11:44 от Филоненко Михаил » Записан
Denjs
Гость
« Ответ #7 : Июнь 16, 2011, 11:23 »

Python тоже самое что c++, только не надо писать типы. На и конструктор-деструктор по-другому называются.
Не совсем. у питона идиотская идея использовать невидимые символы как синтактически обязательный элемент (отступы мать его.В начале думается что "красиво", а когда ты случайно пробел с табом спутаешь и будешь искать его полчаса где он там куда забрался... или когда тебе надо будет код реструктуризировать - что то из if вынести или наоборот и т.п. - отгребешь по полной. ).
Я молчу про собственную ("отмороженную" имхо) логику работы во многих моментах (списочные типы и массивы - _логика_ работы с ними отличается от массивов и списков паскаля и С\С++ - самое первое что бросается в глаза).
У питона свой гемор, который совершенно его отличает от с++. Да и от других языков тоже.
Питон - это не с++ даже больше, чем 1цэ не с++.

Цитировать
стоп, там жеш gc для переменных определенных с помощью var. и оператор delete 'object.property'.
А как быть с функциями? кроме того я не хочу руками принудительно все чистить - "это издевательство"...

хотя знаете... наверное можно вести реестр всех ".property" - до запуска скрипта и сранивать его с тем что мы имеем после - и при завершении скрипта - принудительно вычищать все новодобавленное... это мысль, и мысль вполне доступная для возложения её на плечи фреймворка и препроцесора в него всроенного ... (я вполне могу ввести какой моификатор типа static или global и при загрузке преобразовать его в нужное удобоваримое...)...
спасибо, я попробую поиграться...

« Последнее редактирование: Июнь 16, 2011, 11:35 от Denjs » Записан
asvil
Гость
« Ответ #8 : Июнь 16, 2011, 11:51 »

Функция - это globalObject["имя функции"].

Да, перефразируя вас, "можно запоминать что было в глобальном объекте ДО и удалять все лишнее добавившееся после. var будут удалены автоматически".

Пайтон.
Блокноты решают проблему табов, и вообще я как бы только пробелы использую всегда. Да рефакторинг в пайтоне будет усложнен. С другой стороны выделил текст и пользуешься табом или шифт-табом.
Записан
iRQSX
Гость
« Ответ #9 : Июнь 28, 2011, 10:22 »

Филоненко Михаил, наконец то появилось время проверить на практике код который вы привели и посмотреть вашу программу... Возникли вопросы) Вот выдержка из класса globalscriptengine:
Код
C++ (Qt)
 
int GlobalScriptEngine::main()
{...
QScriptValue global = staticEngine->globalObject();
   // add a 'system' object
   QScriptValue system = staticEngine->newObject();
   global.setProperty("system", system);
  [b] QScriptValue script = staticEngine->newObject();
   global.setProperty("script", script);[/b]
...
Ну хорошо создали мы новый класс в скрипте, только я не пойму что мы с ним делаем:
Код
C++ (Qt)
bool loadFile(const QString& fileName, QScriptEngine *engine, QScriptValue& retValue)
{...
QScriptValue script = engine->globalObject().property("script");
   QScriptValue oldFilePathValue = script.property("absoluteFilePath");
   QScriptValue oldPathValue = script.property("absolutePath");
   script.setProperty("absoluteFilePath", engine->toScriptValue(absoluteFileName));
   script.setProperty("absolutePath", engine->toScriptValue(absolutePath));
 
   retValue = engine->evaluate(contents, fileInfo.absoluteFilePath(), lineNumber);
   if (engine->hasUncaughtException()) {
     QStringList backtrace = engine->uncaughtExceptionBacktrace();
     qWarning() << QString("    %1\n%2\n\n").arg(retValue.toString()).arg(backtrace.join("\n"));
     return true;
   }
 
   script.setProperty("absoluteFilePath", oldFilePathValue);
   script.setProperty("absolutePath", oldPathValue);
...
 
script = engine->globalObject().property("script"); - у меня это дело равно NULL и в коде ниже смысла не вижу
Вот и не могу дойти до сути, хотя чувствую что вся "фишка" где то тут. Лично мне главное, что бы когда скрипт загрузился и ему назначились некоторые свойства (например script.setProperty("ui", engine->newQObject(usermodules::loadUI(fileName+".ui")))Подмигивающий и если этот скрипт загружает другой, и которого тоже есть свой ui, то нужно что бы первый скрипт обращался к своему ui, а второй скрипт к своему. То есть по сути каждый скрипт по сути нужно завернуть в свой класс-объект. у меня же все свойсва(тот же ui ) храняться в GlobalObgect.
« Последнее редактирование: Июнь 28, 2011, 10:26 от iRQSX » Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.285 секунд. Запросов: 21.