PHP расширение и Go

14 minute read

Пока все меряются паузами GC в новой версии Go, я потихоньку линкую его к PHP. Да-да, именно так, в свежем Go появилась возможность билдить код как статическую или динамическую библиотеку. Таким образом, теперь можно использовать код на Go в самых разных сишных проектах, в том числе и для PHP расширений.

Конечно, с практической стороны, в таком использовании Go мало пользы, но вполне вероятно, что в будущем этот найдет применение.

К сожалению, я не обладают большим опытом работы с сишным кодом, поэтому буду рад любым дополнениям и исправлениями.

В этом эксперименте использоваться самый свежий Go на данный момент - 1.5beta2.

Правильно готовим Go

И так, начнём с поисков последней версии Go. Я достаточно ленив, поэтому использую gvm для управлениями версиями языка на своей машине. Используя gvm устанавливаемые последнюю версию Go.

% gvm install go1.5beta2
% gvm use go1.5beta2

Подготовим наш Go проект, который будет собираться в сишную либу. Файл src/arch/main.go:

package main

import "C"
import (
    "fmt"
)

func main() {

}

//export HelloFromGo
func HelloFromGo() {
    fmt.Println("hello")
}

Как видите, мы используем cgo, помечаем нужные нам функции как //export НазваниеФункцииВСишномКоде и, что совсем уж странно, тут есть функция main. Не переживайте, она не будет нам мешать - компоновщик в Go достаточно умен.

На самом, деле вся магия всего лишь в одном параметре для сборки: buildmode. Мы указываем его значение как -buildmode=c-archive и это позволяет собрать статичную либу:

Собирать перечисленные main пакеты и все пакеты, которые они импортируют в архив для языка C. Только необходимые функции будут помеченные как экспортируемые. Не main пакеты будут игнорироваться.

Получить больше информации в об этом параметре можно в хелпе:

% go help buildmode

Вот так выглядит структуру проекта для нашего экспериментирования:

|- src/
    |- arch/
          |- main.go
|- lib/
|- php/

Папка src используется для Go пакетов. Папка lib нужна для результатов go build и в ней будем хранить нашу статичную либу. Ну а в папке php будем пилить расширение.

Теперь можно собирать либу:

% env GOPATH=$(pwd) go build -buildmode=c-archive -o ./lib/libhello.a -v arch

В результате, в папке lib у нас будет два файла: libhello.h и libhello.a. Это именно то, что нам нужно.

Не смущайтесь формату команды. Я устанавливаю переменную GOPATH для удобства работы с проектом.

Учимся писать PHP расширения

Прежде всего, делаем скелет расширения. Сначала нам необходим файл с прототипом публичных функций, которые будут реализованы в нашем расширении и которые будут доступны в пользовательском пространстве PHP ./php/hello.def.

Hello()

Начнем с вот такого простого концепта. Пока нам важно только проверить работоспособность нашей идеи.

Запускаем специальный скрипт, который сгенерирует скелет расширения.

% cd ./php
% ~/.phpbrew/build/php-5.6.10/ext/ext_skel --extname=hello --proto=./hello.def \
--skel=/home/artem/.phpbrew/build/php-5.6.10/ext/skeleton

Я использую phpbrew для PHP, так же как gvm для Go. Это сильно упрощает поддержание актуальной версии PHP на моей машине. Если вы не хотите использовать phpbrew, то вам придется самим затянуть исходники.

  • --extname=hello - Название расширения.
  • --proto=./hello.def - Указываем файл с прототипом функций.
  • --skel=/home/artem/.phpbrew/build/php-5.6.10/ext/skeleton Стандартная заготовка для расширений.

После запуска этого скрипта, у вас появится папка hello, в которой будет все необходимое для расширения. Не забывайте про утилиту phpize

% cd ./php/hello
% phpize

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

PHP_ARG_WITH(hello, for hello support,
[  --with-hello             Include hello support])

if test "$PHP_HELLO" != "no"; then

  PHP_SUBST(HELLO_SHARED_LIBADD)

  PHP_ADD_LIBRARY_WITH_PATH(hello, ../../lib, HELLO_SHARED_LIBADD)
fi

PHP_ADD_LIBRARY_WITH_PATH - таким образом мы указывает что расширение нужно будет собирать с нашей либой.

И теперь используем функцию из Go в нашем расширении:

#include "../../lib/libhello.h"

//...

PHP_FUNCTION(Hello)
{
    if (zend_parse_parameters_none() == FAILURE) {
        return;
    }

    HelloFromGo();
}

Все готово для сборки нашего расширения:

% ./configure
% make && make install

Осталось подключить сошку в php.ini и написать маленький тест.

extension=hello.so

И используем новую функцию:

<php

Hello();

Худо-бедно это работает. Конечно, я и в коем случае не рекомендую использовать это(ну, по крайней мере пока). Кроме того, пока так и не получилось заставить работать такое расширение стабильно и без периодических сегфолтов. Однако, сама идея и возможность реализации греют душу.

Весь код можно посмотреть на гитхабе.

Ссылки