Записки программиста Программирование и не только

20Мар/120

Отправляем объект из внешней библиотеки в скрипт

В Qt, как известно, есть замечательная вещь как QScript, позволяющая использовать ECMA скрипт.
В данный скриптовый движок возможно запихивать как отдельные функции, так и целые объекты.
А можно ли запихать в движок объект из внешней библиотеки (.dll, .so etc)?

Оказывается, что можно.

Делается это так:
1. Для начала напишем библиотеку.
Создаём обычный консольный проект. Назовём его plugin.
В .pro файле меняем строку TEMPLATE = app на TEMPLATE = lib

Удаляем файл main.cpp, он нам не нужен.
Добавляем файлы plugin.h, plugin.cpp, plugin_interface.h

plugin_interface.h - это интерфейс нашей библиотеки, который мы в дальнейшем будем использовать в основном приложении.

plugin.h:

#include <QObject>
#include <QtPlugin>
#include "plugin_interface.h"

//Класс будет просто давать возможность задавать значение и возвращать его потом
class ScriptObject : public QObject, ScriptInterface
{
    Q_OBJECT
    //Указываем интерфейс
    Q_INTERFACES(ScriptInterface)
public:
    explicit ScriptObject(QObject *parent = 0);

public slots:
    void set(quint16 aNewValue) { value = aNewValue; }
    quint16 get() { return value; }

    QObject* getObject() { return this; }

private:
    quint16 value;
};

plugin.cpp:

#include "scriptplugin.h"
//Конструктор обязательно надо описать в cpp файле
ScriptObject::ScriptObject(QObject *parent) :
    QObject(parent),
    value(0)
{}

//Указываем имя экспортируемого объекта
Q_EXPORT_PLUGIN2(scriptobject, ScriptObject);

plugin_interface.h:

//Все методы класса должны быть виртуальными, т.е. чистое описание интерфейса, который мы потом реализуем в потомке.
class ScriptInterface
{
public:
    virtual ~ScriptInterface() {}

    virtual quint16 get() = 0;
    virtual void set(quint16) = 0;
    virtual QObject* getObject() = 0;
};

//Объявляем класс как интерфейсный
Q_DECLARE_INTERFACE(ScriptInterface,
                    "Script.Plugin/0.1");

Всё, библиотека готова. После компиляции (под Win) получаем два файла plugin.dll и libplugin.a

Создаём еще один проект.
Закидываем в него заголовочный файл с интерфейсом.
Я создал GUI проект с основным классом Widget.
На форму набросал два TextEdit и кнопку.

widget.h:

#include <QWidget>
#include <QScriptEngine>
#include <QDir>
#include <QFileInfoList>
#include <QPluginLoader>
#include <QLibrary>

#include "plugininterface.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT
    
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void run(); //Слот в котором будет выполняться скрипт
    
private:
    Ui::Widget *ui;

    QPluginLoader *pluginLoader; //Загрузчик библиотеки
};

Реализация.
Предполагается, что библиотека лежит в папке plugins рядом с исполняемым файлом.
Для загрузки библиотеки надо:

QDir pluginsDir(QString("%1/plugins").arg(qApp->applicationDirPath()));
    QFileInfoList pluginsFiles = pluginsDir.entryInfoList(QDir::Files|QDir::NoDotAndDotDot,
                                                              QDir::Name);

    for(int i = 0; i < pluginsFiles.size(); i++)
    {
        if(QLibrary::isLibrary(pluginsFiles.value(i).absoluteFilePath())) //Проверяем, библиотека ли это
        {
            pluginLoader = new QPluginLoader(pluginsFiles.value(i).absoluteFilePath()); //Нашли? Загружаем.
            break;
        }
    }

Библиотека загружена (ну или не загружена, если файл не найден)

Далее, получаем объект и загружаем его в скриптовый движок.

if(pluginLoader)
    {
        QObject *plugin = pluginLoader->instance(); //Получаем корневой объект из библиотеки
        if (plugin)
        {
            ScriptInterface *sc = qobject_cast<ScriptInterface*>(plugin); //Преобразуем корневой объект в наш

            QScriptEngine engine;

            QScriptValue scObj = engine.newQObject(sc->getObject(), QScriptEngine::ScriptOwnership); //Засовываем наш объект в движок

            engine.globalObject().setProperty("ScObj", scObj); //Даём ему имя и регистрируем

            QScriptValue result = engine.evaluate(ui->scriptEdit->toPlainText()); //Выполняем (текст берем из первого TextEdit)
            //Результат отправляем во второй TextEdit
            if(result.isError())
            {
                ui->logEdit->append(QString("Script error").append(QString::fromLatin1("%1: %2")
                                                                   .arg(result.property("lineNumber").toInt32())
                                                                   .arg(result.toString())));
            }
            else
            {
                ui->logEdit->append(result.toString());
            }
            engine.collectGarbage();
        }
    }

Вот и всё.

Теперь в скрипте можно написать:

ScObj.set(9);
ScObj.get();

И он вернёт нам 9.

Комментарии (0) Пинги (0)

Пока нет комментариев.


Leave a comment

Нет обратных ссылок на эту запись.