Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP Extension with C++

I recently started looking at writing PHP extensions and I read through this article, which describes a starting point for building an extension using C++. As I've started to customize, I've run into a hitch trying to split some of the functionality into a separate file. Everything compiles and links without problems, but an error occurs when I try to actually use the extension. The exact message is:

$ php -dextension=test.so -r "var_dump( new Test );"
php: symbol lookup error: /etc/php/ext/test.so: undefined symbol: _ZN9ContainerI4TestEC1EP17_zend_class_entry

I tried this on two computers and both experience the same problem. I understand that it can't find the actual implementation for the Container constructor, but I don't know how to get it to look in the right place.

I've tried to cut out as much of the fluff as I can before posting here, but there is still a lot of cruft in the php interface code. The code is as follows:

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;
}

I'm building it using the following commands:

$ phpize
$ ./configure --enable-test
$ make && make install
$ php -dextension=test.so -r "var_dump( new Test );"

Thanks for any help you can offer

like image 545
Nycto Avatar asked Dec 03 '10 05:12

Nycto


1 Answers

When compiling template classes, the implementation must be available from the header file since the C++ complier needs the template arguments in order to compile a template class. If a C++ compiler were to compile just internals.cpp, it would not be able to create any code as the type T is not known. It would only be able to compile it in the context of interface.cpp but the actual implementation of Container is not available to the compiler at that time.

So the problem is simply that Complier is never compiled.

You could simply add the implementation of Compiler below its declaration, in the internals.h file.

like image 169
David Nordvall Avatar answered Sep 22 '22 13:09

David Nordvall