Расширение PHP с C++
Недавно я начал изучать написание расширений PHP и прочитал эту статью, в которой описывается отправная точка для создания расширения с использованием C++. Как я начал настраивать, я столкнулся с проблемой, пытаясь разделить некоторые функции в отдельный файл. Все компилируется и связывается без проблем, но возникает ошибка, когда я пытаюсь использовать расширение. Точное сообщение:
$ php -dextension=test.so -r "var_dump( new Test );"
php: symbol lookup error: /etc/php/ext/test.so: undefined symbol: _ZN9ContainerI4TestEC1EP17_zend_class_entry
Я пробовал это на двух компьютерах, и оба испытывают ту же проблему. Я понимаю, что он не может найти фактическую реализацию для конструктора контейнера, но я не знаю, как заставить его выглядеть в нужном месте.
Я пытался вырезать столько пуха, сколько смогу, прежде чем публиковать здесь, но в коде интерфейса php все еще много разногласий. Код выглядит следующим образом:
config.m4:
PHP_ARG_ENABLE(test,
[Whether to enable the "test" extension],
[ --enable-test Enable "test" extension support])
if test $PHP_TEST != "no"; then
PHP_REQUIRE_CXX()
PHP_SUBST(TEST_SHARED_LIBADD)
PHP_ADD_LIBRARY(stdc++, 1, TEST_SHARED_LIBADD)
PHP_NEW_EXTENSION(test, interface.cpp internals.cpp, $ext_shared)
fi
interface.h:
#ifndef INTERFACE_H_
#define INTERFACE_H_
#define PHP_TEST_EXTNAME "test"
#define PHP_TEST_EXTVER "0.1"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#endif
interface.cpp:
#include "interface.h"
#include "internals.h"
#include "php.h"
class Test {};
extern zend_module_entry test_module_entry;
zend_object_handlers test_object_handlers;
zend_class_entry *test_ce;
void test_free_storage(void *object TSRMLS_DC)
{
delete (Container<Test> *) object;
}
zend_object_value test_create_handler( zend_class_entry* classInfo TSRMLS_DC )
{
Container<Test> *obj = new Container<Test>( classInfo );
zend_object_value retval;
retval.handle = zend_objects_store_put(
obj, NULL, test_free_storage, NULL TSRMLS_CC
);
retval.handlers = &test_object_handlers;
return retval;
}
PHP_METHOD(Test, __construct)
{
Test* test = new Test;
Container<Test> *obj = (Container<Test> *) zend_object_store_get_object(getThis() TSRMLS_CC);
obj->cpp = test;
}
function_entry test_methods[] = {
PHP_ME(Test, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
{NULL, NULL, NULL}
};
PHP_MINIT_FUNCTION(test)
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Test", test_methods);
test_ce = zend_register_internal_class(&ce TSRMLS_CC);
test_ce->create_object = test_create_handler;
memcpy(
&test_object_handlers,
zend_get_std_object_handlers(),
sizeof(zend_object_handlers)
);
test_object_handlers.clone_obj = NULL;
return SUCCESS;
}
zend_module_entry test_module_entry = {
STANDARD_MODULE_HEADER,
PHP_TEST_EXTNAME,
NULL, /* Functions */
PHP_MINIT(test), /* MINIT */
NULL, /* MSHUTDOWN */
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
NULL, /* MINFO */
PHP_TEST_EXTVER,
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_TEST
extern "C" {
ZEND_GET_MODULE(test)
}
#endif
internals.h:
#ifndef INTERNALS_H_
#define INTERNALS_H_
#include "zend.h"
template <class T>
class Container
{
private:
zend_object zend;
public:
T* cpp;
Container ( zend_class_entry* classInfo );
};
#endif /* INTERNALS_H_ */
internals.cpp
#include "internals.h"
#include "zend.h"
template <class T>
Container<T>::Container ( zend_class_entry* classInfo )
: zend()
{
zend.ce = classInfo;
}
Я строю его, используя следующие команды:
$ phpize
$ ./configure --enable-test
$ make && make install
$ php -dextension=test.so -r "var_dump( new Test );"
Спасибо за любую помощь, которую вы можете предложить
2 ответа
При компиляции шаблонных классов реализация должна быть доступна из заголовочного файла, так как компилятору C++ нужны аргументы шаблона для компиляции шаблонного класса. Если бы компилятор C++ компилировал только internals.cpp, он не смог бы создать никакого кода, так как тип T не известен. Он сможет скомпилировать его только в контексте interface.cpp, но фактическая реализация Container не доступна для компилятора в то время.
Так что проблема в том, что Complier никогда не компилируется.
Вы можете просто добавить реализацию Compiler ниже его объявления, в файле internals.h.
Вы создали шаблон, но никогда не создавали его экземпляр в расширении - шаблоны по определению не являются чем-то конкретным, но создаются "по требованию", когда что-то нужно. Однако это создание происходит во время компиляции, а не во время выполнения, поэтому вашему расширению нужны все шаблоны, которые будут использоваться вашими приложениями PHP для явной реализации.
Это можно сделать, просто создав его, поместив его в internals.cpp
template class Container<float>;
или любой другой тип контейнера, который вам нужен.