Каждый php программист, хотя бы раз в жизни, задумывался о написания своего расширения для php. Сегодня я хочу рассказать о том как написать расширение с помощью библиотеки PHP-CPP. На примере вывода алерта с кнопкой, с помощью gtk.



Тем кому интересен процесс прошу под кат.

Зависимости:

1. libgtk2.0-dev
2. php7.0-dev
3. php-cpp — библиотека которая нам поможет все разработать

Первые две зависимости ставятся из репозиториев вашей linux дистрибутива.

$ apt-get install libgtk2.0-dev php7.0-dev

Для установки php-cpp клонируем репозиторий с github.com php-cpp, я всё буду собирать в tmp директории:

$ cd /tmp/
$ git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git
$ cd PHP-CPP/ # переходим в директорию с исходниками
$ make # этой командой собираем библиотеку
$ sudo make install # и устанавливаем в систему


И так всё готово, теперь мы можем приступить к разработки, создаём директорию с проектом у меня она называется gtkPHP7. Прежде чем начать, скачаем скелетон будущего расширения по ссылке empty-extension.zip, и распаковываем архив в папку проекта. После чего, у нас появятся следующие файлы:

1. extensions.ini
2. Makefile
3. main.cpp
4.
Переименуем extensions.ini и откроем его на редактирование. Заменим строку 'extensions=extensions.ini' на 'extensions=gtkphp7.ini'. Далее, нам необходимо внести изменения в файл Makefile, отредактировав в нём следующие строки:

NAME                            = gtkphp7
INI_DIR                         =       /etc/php/7.0/mods-available/
COMPILER_LIBS           =       `pkg-config --cflags --libs gtk+-2.0`

all:                                    ${OBJECTS} ${EXTENSION} 

${EXTENSION}:                   ${OBJECTS}
                                                ${LINKER} ${LINKER_FLAGS} -o $@ ${OBJECTS} ${LINKER_DEPENDENCIES} ${COMPILER_LIBS}

NAME — имя нашего extensions
INI_DIR — директории для конфигураций модулей php, у меня получилась /etc/php/7.0/mods-available/ ваше же может отличаться.
COMPILER_LIBS — подгружает необходимые нам библиотеки в данном случае gtk
И последним я добавил COMPILER_LIBS в цель all.


Перейдём, непосредственно, к разработки расширения. Для этого я установил clion от jetbrains и импортировал проект в ide. Все остальные работы мы будем проводить в файле main.cpp. Я решил разработать класс который будет инстанцироваться в php, и с последовательными методами формировать окошко alert'а.

#include <phpcpp.h>
#include <iostream>
#include <gtk/gtk.h>

class Gtk : public Php::Base {
private:
    GtkWidget *_window;
    char *_titleWindow;
    char *_buttonTitle;
    GtkWidget *_button;
    Php::Value callB;

public:
    Php::Value setTitle(Php::Parameters ¶ms);
    Php::Value setButtonTitle(Php::Parameters ¶ms);
    Php::Value createWindow();
    Php::Value setButton();
    static void callback(GtkButton *button, gpointer data);
    Php::Value render();
};

Это прототип класса расширения:

1. setTitle — устанавливает заголовок окна
2. setButtonTitle — заголовок кнопки
3. createWindow — создаёт окно
4. setButton — создаёт кнопку и устанавливает в окне
5. callback — кэлбэк функция которая вызывается при клике на кнопку
6. render — выводит окно с кнопкой на экран

Первые два метода опустим, так как они просто устанавливают соответствующие переменные для использования в качестве заголовков.

/**
 * create window gtk
 * @return Gtk
 */
Php::Value Gtk::createWindow() {
    int argc = 0;
    char **argv = NULL;
    gtk_init(&argc, &argv);
    _window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(_window), _titleWindow);
    gtk_container_set_border_width(GTK_CONTAINER(_window), 50);
    return this;
}

Для того что бы инициализировать gtk, мы создали переменные argc и argv соответственно передав им 0 и NULL, как будто бы, приложение было запущено без параметров. Окно мы создаём методом 'gtk_window_new' и присваиваем переменной _window — это приватный параметр класса.

/**
 * set button gtk
 * @return Gtk
 */
Php::Value Gtk::setButton() {
    _button = gtk_button_new_with_label(_buttonTitle);
    gtk_container_add(GTK_CONTAINER(_window), _button);
    g_signal_connect(G_OBJECT(_button), "clicked", G_CALLBACK(&Gtk::callback), G_OBJECT(_window));
    return this;
}

Этот метод создаёт виджет кнопки и присваивает её окну _window. Функция g_signal_connect отслеживает нажатие кнопки мы передаём в функцию следующие параметры:

1. объект кнопки
2. тип действия
3. callback функция (она не должна быть членом класса, другими словами она должна быть либо статичной либо отдельной функцией)
4. здесь мы передаём объект окна для использования в callback функции.

Разберём callback-функцию:

/**
 * callback click
 * @param button
 * @param window
 */
void Gtk::callback(GtkButton *button, gpointer window) {
    gtk_widget_destroy(GTK_WIDGET(window));
    gtk_main_quit();
}

Второй параметр это как рас, то что было передано 4 параметром в функции выше, первый собственно наша кнопка. Функция проста она просто выходит из gtk. Ну и последнем рассмотрим функцию render:

/**
 * render alert
 * @return
 */
Php::Value Gtk::render() {
    gtk_widget_show_all(_window);
    gtk_main();
    return true;
}

В общем метод показывает все виджеты окна, и запускает gtk. После остановки, посредством callback функции она вернёт true.

Последним шагом, мы должны зарегистрировать наш класс и его методы:

/**
 *  tell the compiler that the get_module is a pure C function
 */
extern "C" {
/**
 *  Function that is called by PHP right after the PHP process
 *  has started, and that returns an address of an internal PHP
 *  strucure with all the details and features of your extension
 *
 *  @return void*   a pointer to an address that is understood by PHP
 */
PHPCPP_EXPORT void *get_module() {
    static Php::Extension extension("gtkphp7", "1.0");
    Php::Class<Gtk> gtk("Gtk"); // регистрируем класс
    gtk.method<&Gtk::setTitle>("setTitle"); // так регистрируем методы класса
    gtk.method<&Gtk::setButtonTitle>("setButtonTittle");
    gtk.method<&Gtk::setButton>("setButton");
    gtk.method<&Gtk::createWindow>("createWindow");
    gtk.method<&Gtk::render>("render");
    extension.add(std::move(gtk)); // регистрируем класс и его метод как нативные 
    // return the extension
    return extension;
}
}

Полный код main.cpp под спойлером:

main.cpp
#include <phpcpp.h>
#include <iostream>
#include <gtk/gtk.h>

class Gtk : public Php::Base {
private:
    GtkWidget *_window;
    char *_titleWindow;
    char *_buttonTitle;
    GtkWidget *_button;
    Php::Value callB;

public:
    Php::Value setTitle(Php::Parameters ¶ms);
    Php::Value setButtonTitle(Php::Parameters ¶ms);
    Php::Value createWindow();
    Php::Value setButton();
    static void callback(GtkButton *button, gpointer data);
    Php::Value render();
};

/**
 * set title to window
 * @param params
 * @return Gtk
 */
Php::Value Gtk::setTitle(Php::Parameters ¶ms) {
    std::string title = params[0];
    _titleWindow = new char[title.size() + 1];
    std::copy(title.begin(), title.end(), _titleWindow);
    _titleWindow[title.size()] = '\0';
    return this;
}

/**
 * set button title
 * @param params
 * @return Gtk
 */
Php::Value Gtk::setButtonTitle(Php::Parameters ¶ms) {
    std::string title = params[0];
    _buttonTitle = new char[title.size() + 1];
    std::copy(title.begin(), title.end(), _buttonTitle);
    _buttonTitle[title.size()] = '\0';
    return this;
}

/**
 * create window gtk
 * @return Gtk
 */
Php::Value Gtk::createWindow() {
    int argc = 0;
    char **argv = NULL;
    gtk_init(&argc, &argv);
    _window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(_window), _titleWindow);
    gtk_container_set_border_width(GTK_CONTAINER(_window), 50);
    return this;
}

/**
 * set button gtk
 * @return Gtk
 */
Php::Value Gtk::setButton() {
    _button = gtk_button_new_with_label(_buttonTitle);
    gtk_container_add(GTK_CONTAINER(_window), _button);
    std::cout << callB << std::endl;
    g_signal_connect(G_OBJECT(_button), "clicked", G_CALLBACK(&Gtk::callback), G_OBJECT(_window));
    return this;
}

/**
 * callback click
 * @param button
 * @param window
 */
void Gtk::callback(GtkButton *button, gpointer window) {
    gtk_widget_destroy(GTK_WIDGET(window));
    gtk_main_quit();
}

/**
 * render alert
 * @return
 */
Php::Value Gtk::render() {
    gtk_widget_show_all(_window);
    gtk_main();
    return true;
}

/**
 *  tell the compiler that the get_module is a pure C function
 */
extern "C" {
/**
 *  Function that is called by PHP right after the PHP process
 *  has started, and that returns an address of an internal PHP
 *  strucure with all the details and features of your extension
 *
 *  @return void*   a pointer to an address that is understood by PHP
 */
PHPCPP_EXPORT void *get_module() {
    static Php::Extension extension("gtkphp7", "1.0");
    Php::Class<Gtk> gtk("Gtk");
    gtk.method<&Gtk::setTitle>("setTitle");
    gtk.method<&Gtk::setButtonTitle>("setButtonTittle");
    gtk.method<&Gtk::setButton>("setButton");
    gtk.method<&Gtk::createWindow>("createWindow");
    gtk.method<&Gtk::render>("render");
    extension.add(std::move(gtk));
    // return the extension
    return extension;
}
}


Переходим к следующему шагу, компиляции и установки расширения:

$ make
$ sudo make install

После установки делаем симлинки для подключения или phpenmod:

$ sudo ln -s /etc/php/7.0/mods-available/gtkphp7.ini /etc/php/7.0/cli/conf.d/20-gtkphp7.ini
$ sudo service php7.0-fpm restart # перезапускаем php
$ php -m | grep gtkphp7 # проверяем есть ли расширения

Для пересборки и переустановки расширения достаточно выполнить:

$ make clean
$ make
$ sudo make install

Для теста был написан простой скрипт на php:

<?php
function alert($title) {
  $gtk = new Gtk;
  return $gtk
    ->setTitle($title)
    ->setButtonTittle("Ok")
    ->createWindow()
    ->setButton()
    ->render();
}

if(alert("Hellow habr")) {
   alert("Hellow again");
}

Результат скрипта, те самые скриншоты в заголовке статьи… Для удобства работы над расширением была написана функция:

void dump(Php::Value dumping) {
   Php::call("var_dump",dumping);
}

Она вызывает php функцию var_dump, довольно удобно делать дампы переменных массивов php и т.д.


В качестве выводов приведу несколько ссылок:

1. Документация по php-cpp
2. Репозиторий с примером из статьи
p.s. На c++ это один из первых опытов, по этому, если что не так с кодом, пишите в комментарии, и хороших выходных.

Поделиться с друзьями
-->

Комментарии (20)


  1. S_A
    02.10.2016 08:47

    У gtk есть менее многословные биндинги. Оно конечно занимательно, но вот реальных desktop-проектов на php я не встречал. Есть какие-нибудь интересные примеры? Гугление showcases выдает какие-то калькуляторы…



    1. empr
      02.10.2016 16:19
      +2

      Существует RAD-среда DevelNext, в которой десктопные программы пишутся на JPHP — аналоге PHP, компилируемом в байт-код для JVM. UI построен на JavaFX. Есть возможность писать свои расширения на Java — к примеру, я реализовал в несколько строк подхват сторонних DLLок через JNA. К сожалению, автор компилятора и среды позиционирует свой продукт, как решение для начинающих программистов, поэтому вместо нормального редактора файлов проекта, предпочтение отдаётся всевозможным drag&drop конструкторам. Вероятно, со временем это изменится.

      Скриншот


    1. wxmaper
      04.10.2016 04:37
      +1

      В далёком 2011 делал вот такое чудище на php:

      Скриншот
      http://wxmaper.ru/images/kvvkdb-0-3.png


      1. antoo
        04.10.2016 04:47

        Первая программа случайно не на PHP Devel Studio написана? Иконка очень напоминает логотип Soul Engine.


        1. wxmaper
          04.10.2016 04:52

          В точку, это был именно он.


      1. S_A
        04.10.2016 08:56

        Второй скрин очень симпатичен. Я не предубежденный противник php, я на нем по работе иногда пишу. Сайты, да. А десктопные gui я писал много на чем, но не на php, сложилось впечатление, что нормальных либ толком нет.

        Думаю php привычен для сайтов в большей степени ровно из-за всяких $_SERVER, $_POST и иже с ними.


  1. KAndy
    02.10.2016 10:42
    -2

    Библиотека неплохая, вот только поддерживаеться только за денежку. Пару раз наступил на баги или отсутсвие функциональности (например отсуствие возможности устанавливать статические переменные) и никакого ответа от разроботчиков…


    1. lnroma
      02.10.2016 11:57

      В смысле устанавливать статические переменные? как параметры класса?


      1. KAndy
        02.10.2016 14:10

        аналог PHP кода:

        <?php 
        
        class A {
           static $a = null;
           public function getA(){
              self::$a = new A(); // << вот этого нет
              return self::$a;
           }
        
        }
        


        1. roswell
          02.10.2016 19:49
          +1

          Эмулятор $this?


        1. lnroma
          02.10.2016 19:52

          Return this;? В примере не то.


    1. rotor
      02.10.2016 21:49

      например отсуствие возможности устанавливать статические переменные

      так вы попробуйте в документацию заглянуть.
      #include <phpcpp.h>
      class YourCustomClass : public Php::Base {};
      extern "C" {
          PHPCPP_EXPORT void *get_module() {
              static Php::Extension ext("YourCustomExtention", "1.0");
              Php::Class<YourCustomClass> cl("YourCustomClass");
              cl.property<YourCustomClass>("staticProperty", std::nullptr, Php::Static);
              ext.add(std::move(gtk));
              return ext;
          }
      }
      

      <?php 
      class YourCustomClass {
         static $staticProperty = null;
      }
      


      1. KAndy
        03.10.2016 09:22

        Это вы задекларировали, а попробуйте поменять это значение в какомто методе…


        1. rotor
          03.10.2016 11:53

          Не сразу увидел ваше дополнение. Да, кажется, доступ к статическим полям не реализован. Его, кстати, не так уж и сложно реализовать. Я примерно представляю как это можно сделать. Просто это никому не было нужно.
          Что касается вашего примера, то у вас статическое поле торчит наружу. Это не очень хорошо с точки зрения архитектуры. И, возможно, требуется рефакторинг. Попробуйте в вашем примере заменить static $a на private static $a и проблема сразу исчезнет.
          А вообще, опишите исходную задачу, попробую накидать решение.
          В настоящий момент в библиотеке есть практически все для решения основных задач, возникающих при написании расширений.


  1. azsx
    02.10.2016 13:49

    оффтопик
    Правильно ли я понимаю, что когда выводят графику на десктопе на многих других языках программирования, например, на pyton, по сути используют такие же библиотеки и методы? То есть это универсальные методы, несколько отличается только реализация для разных языков?


    1. GamePad64
      02.10.2016 17:01
      +1

      Да, там зачастую одни и те же библиотеки под капотом. Библиотеки зачастую пишутся так, чтобы функции были доступны через FFI. Тогда их можно использовать из других языков, создавая специфичную для данного языка, обёртку.


    1. pilot114
      02.10.2016 19:50

      Если обобщить вообще для всех языков, 99% процентов внешнего взаимодействия прикладных программ осуществляется посредством вызова syscall's — «универсальных методов», реализуемых самой операционной системой =) касательно графики, да — везде используются одни и те же исполнители — графические драйвера


  1. crazy_russian
    02.10.2016 19:53
    -1

    Hellow? Really?


    1. lnroma
      02.10.2016 19:53

      Да опечатка