Мануал по разработке расширений

всё, что связанно с разработкой расширений для движка PQEngine

Moderator: ArtMares

WxMaper
User avatar
Администратор
 
Posts: 92
Разрабатывать расширения для движка достаточно просто. Постараюсь максимально подробно описать процесс их создания на примере класса QDir.
Для начала нужно обзавестись необходимыми инструментами: Visual Studio нужна для Qt, без неё вы не сможете собирать проекты.
Запускаем Qt и создаем новый проект: Библиотеки - Библиотека C++ - Статическая библиотека - MyFirstExtension - Требуемые модули - QtCore - Готово Сразу переходим к редактированию файла проекта MyFirstExtension.pro. Здесь нам нужно указать путь к заголовочным файлам PQEngine, для этого добавляем в самый конец файла такие строки:
Code:
# путь указывается в кавычках и с прямыми слешами
# кириллические символы в путях не допускаются (в том числе в пути к самому проекту)
PQENGINE_SRC_PATH="D:/include/pqengine-0.5.1"
CONFIG += qt c++11
DEFINES += PQSTATIC
INCLUDEPATH += $${PQENGINE_SRC_PATH}/pqengine-core/\
    $${PQENGINE_SRC_PATH}/pqengine-core/classes\
    $${PQENGINE_SRC_PATH}/pqengine-core/private
Сохраняем файл (ctrl+s) и переходим к заголовочному файлу будущего расширения myfirstextension.h
Первым делом подключаем заголовочный файл движка pengine.h:
man4.jpg
man4.jpg (18.31 KiB) Viewed 6060 times
Если строка подчеркнулась красной или оранжевой линией, то нужно проверить правильно ли вы указали пути в файле проекта. Двигаемся дальше...
Класс необходимо унаследовать от интерфейса IPQExtension и указать ему спецификацию PQDLAPI
Для объявления части методов интерфейса доступны специальные макросы, их можно найти в исходниках движка в файле ipqengineext.h
Для нашего модуля MyFirstExtension реализация интерфейса будет выглядеть так:
Code:
#include 
class PQDLAPI MyFirstExtension : public IPQExtension
{
    // основные методы
    QMetaObjectList classes();
    bool start();
    bool finalize();
    // объявление спец-методов
    PQEXT_USE(instance)
    PQEXT_USE(ub_write)
    PQEXT_USE(pre)
    /* 
     * Тут указывается какие функции наш модуль может предоставить движку.
     * 
     * В данном примере таких функций нет,
     * поэтому они заменяются на макросы PQEXT_NO_* :)
     */
    PQEXT_ENTRY_START(MyFirstExtension )
        PQEXT_NO_INSTANCE
        PQEXT_NO_UB_WRITE
        PQEXT_NO_PRE
    PQEXT_ENTRY_END
};
Теперь можно приступить к реализации методов интерфейса
Автогенерация кода рулит :)
Для удобства используйте автогенерацию кода! Вызовите контекстное меню (правый клик мышки) у нереализованной функции - Рефакторинг - Добавить реализацию в *.cpp
man5.jpg
А реализовать нужно всего три функции: 
1. Функция bool start() вызывается движком самой первой, до регистрации классов;
2. Функция QMetaObjectList  classes() должна возвратить список объявленных классов, вызывается она после функции start();
3. Функция bool finalize() вызывается в последнюю очередь, когда все классы уже зарегистрированы.
Больше всего нас интересуют только последние две функции, но пока что у нас нет никаких классов, поэтому можно быстро накидать валидный холст для cpp файла:
Code:
#include "myfirstextension.h"
/* не забудьте удалить конструктор класса,
 * т.к. он не объявляется в модуле
MyFirstExtension::MyFirstExtension()
{
}
*/
bool MyFirstExtension::start()
{
    return true;
}
QMetaObjectList MyFirstExtension::classes()
{
    QMetaObjectList myClasses;
    return myClasses;
}
bool MyFirstExtension::finalize()
{
    return true;
}
Работа с модулем на этом пока закончена, теперь можно его скомпилировать чтобы убедится в отсутствии ошибок. Для этого нужно обязательно сменить тип сборки на Release
man6.jpg
man6.jpg (26.18 KiB) Viewed 6060 times
Затем можно приступить к созданию класса-обёртки над QDir.
Немного теории
---------------------------------------------------------
Вся суть создания обёртки заключается в том, что нужно максимально "примитизировать" вызовы методов для PHP. Иными словами: нужно привести каждый аргумент метода к одному из трёх примитивных типов: bool, int или string. Дополнительно движок поддерживает передачу указателей на объекты, типы которых были декларированы в метасистеме Qt.
При этом методы, которые необходимо сделать доступными в PHP, нужно объявить как "вызываемые" (Invokable). Это делается подстановкой макроса Q_INVOKABLE перед объявлением функции.
Дополнительным обязательным требованием является наличие буквы P в начале имени регистрируемого класса. Если вы хотите создать обёртку для класса SomeClass, то имя класс-обёртки должны быть PSomeClass. Для нашего модуля, в котором мы создаём обертку над QDir, имя классы будет PQDir.
Кроме того, класс обязательно должен быть унаследован от QObject, либо от любого его наследника. Не все классы Qt наследуются от QObject, как, например, и реализуемый нами QDir, поэтому просто унаследовав PQDir от QDir мы не сможем завести метасистему Qt, что приведёт к образованию множества ошибок на этапе компиляции проекта.
Эту ситуацию легко исправить двойным наследованием: нужно лишь дополнительно унаследовать наш PQDir от QObject или от PQObject, заголовочный файл которого входит в набор PQEngine. Наследование от PQObject позволит передавать наш объект в методы других объектов PHPQt5, если они будут это поддерживать.
---------------------------------------------------------
Создаём новый класс в нашем проекте: Файл - Новый файл или проект - Файлы и классы - C++ - Класс C++
Имя класса: PQDir
Базовый класс: <Custom>
Простое объявление класса выглядит так:
pqdir.h:
Code:
#ifndef PQDIR_H
#define PQDIR_H
// Подключаем заголовки базового класса
#include 
// Подключаем заголовки объекта PQObject
#include 
// Объявляем класс
class PQDir : public PQObject, public QDir
{
    // Добавляем спец-макросы Qt и PQEngine
    Q_OBJECT
    PQ_OBJECT_EX(QDir)
    Q_PROPERTY(QString path READ path WRITE setPath)
public:
    Q_INVOKABLE explicit PQDir(const QString &path = "");
    ~PQDir();
    Q_INVOKABLE QString path() const;
    Q_INVOKABLE void setPath(const QString &path);
// объявляем *on-свойства
public Q_SLOTS:
    PQ_ON_SIGNAL_DECL_START {
        PQOBJECT_ON_SIGNALS();
    } PQ_ON_SIGNAL_DECL_END
};
#endif // PQDIR_H
Давайте построчно разберём что тут происходит, опустив участки с комментариями:
Макрос Q_OBJECT - обязательный макрос для объектов Qt, он обязателен в любом случае;
Макрос PQ_OBJECT_EX распаковывается в набор обязательных базовых методов объектов PHPQt5;
Макрос Q_PROPERTY объявляет свойства объекта и указывает через какие методы к этому свойству можно обратится для его чтения и/или записи; 
Q_INVOKABLE explicit PQDir(const QString &path = ""); - объявление явного (explicit) "вызываемого" (Q_INVOKABLE ) конструктора нашего класса с одним необязательным аргументом path;
~PQDir(); - объявление деструктора класса;
Q_INVOKABLE QString path() const; - метод для чтения значения свойства path;
Q_INVOKABLE void setPath(const QString &path); - метод для установки значения свойству path. Оба этих метода так же будут доступны в PHP, потому что они объявлены с макросом Q_INVOKABLE.
Блок объявления *on-свойств регистрирует базовые *on-свойства для объектов PHPQt5. Позже я расскажу как декларировать свои сигналы, слоты и *on-свойства.
Реализация всех этих методов будет такой-же прозрачной и простой:
pqdir.cpp:
Code:
#include "pqdir.h"
// подключаем специальный приватный заголовок PQObject
#include "pqobject_private.h"
// вызываем макрос для развертки стандартных методов PQObject
// здесь в первом аргументе указывается имя основного класса, над которым мы создаём обертку
// во втором аргументе нужно указать имя базового класса, который наследуется от QObject
PQOBJECT_STANDARD_METHODS(QDir,PQObject)
// Если бы QDir изначально наследовался от QObject, то вызов был бы таким:
// PQOBJECT_STANDARD_METHODS(QDir,QDir)
PQDir::PQDir(const QString &path)
    : PQObject(0), QDir(path)
{
    declareOnSignals(); // регистрируем объявленные *on-свойства
}
PQDir::~PQDir()
{
}
QString PQDir::path() const
{
    return QDir::path();
}
void PQDir::setPath(const QString &path)
{
    QDir::setPath(path);
}
Это ещё не всё, осталось зарегистрировать наш в модуле. Для этого возвращаемся в исходник модуля и исправляем его:
myfirstextension.cpp
Code:
#include "myfirstextension.h"
#include "pqdir.h"
bool MyFirstExtension::start()
{
    return true;
}
QMetaObjectList MyFirstExtension::classes()
{
    QMetaObjectList myClasses;
    myClasses
Вот, собственно, и всё. Теперь можно собрать проект. На выходе мы получим файл MyFirstExtension.lib, который и будет являться модулем для движка.
Регистрация расширения в PQBuilder
Переходим в папку с установленным сборщиком PQBuilder/extensions/0.5.1 и создаем там папку нашего расширения: myfirstextension. В неё необходимо скопировать заголовочный и объектный файлы нашего модуля: myfirstextension.h и MyFirstExtension.lib, а также нужно создать файл описания модуля pqext.json с примерно таким содержанием:
Code:
{
    "fullname": "My First Extension!",
    "author": "Pasiliy Pupkin",
    "desc": "My first extension for PQEngine",
    "qtdepends": [
        "core"
    ],
    "includes": [
        "myfirstextension.h"
    ],
    "lib": [
        "myfirstextension"
    ],
    "config": [
        [
        ]
    ],
    "modules": [
        "MyFirstExtension"
    ],
    "have_instance": 0,
    "have_ub_write": 0,
    "have_pre": 0
}
Сохраняем это дело, запускаем билдер, создаём новый проект и открываем список расширений
man7.jpg
Ура! Наше расширение появилось в списке. Смело включаем его, дополнительно включаем модуль Widgets, чтобы протестировать что у нас получилось и собираем проект с таким содержимым main.php:
Code:
class MainWindow extends QWidget {
    public function __construct() {
        parent::__construct();
        
        $this->initComponents();
    }
    
    private function initComponents() {
        // тестируем конструктор и свойство
        $qdir = new QDir("somePath");
        pre( "path: " . $qdir->path );
        
        // тестируем методы
        $qdir->setPath("other path");
        pre( "path(): " . $qdir->path() );
        
        // тестируем константу
        pre( "myConstant: " . QDir::myConstant );
    }
}
$mainWindow = new MainWindow;
$mainWindow->show();
qApp::exec();
Запускаем проект и наслаждаемся результатом :)

Вернуться в Разработка расширений

Who is online
Users browsing this forum: No registered users and 3 guests