Для начала нужно обзавестись необходимыми инструментами:
- Qt 5.6.0 for Windows 32-bit (VS 2015, 840 MB)
- Visual Studio 2015 Community
- и заголовочные файлы PQEngine
Запускаем 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
Первым делом подключаем заголовочный файл движка pengine.h:
Если строка подчеркнулась красной или оранжевой линией, то нужно проверить правильно ли вы указали пути в файле проекта. Двигаемся дальше...
Класс необходимо унаследовать от интерфейса 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
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; }
Затем можно приступить к созданию класса-обёртки над 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
Регистрация расширения в 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 }
Ура! Наше расширение появилось в списке. Смело включаем его, дополнительно включаем модуль 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();