8

Following the http://devzone.zend.com/article/4486 tutorial I've tried to wrap a couple of C++ classes to a PHP extension, unfortunately it fails. I hope there is someone who could help me. In order to try simplify the problem resolution I also simplified my classes.

The objective is to have classes that allow me to execute some polygonal operations. Then I've created the Point class and the Polygon class as follows:

polygon.h

#ifndef POLYGON_H
#define POLYGON_H 1

#include <vector>

class Point {
  double __x;
  double __y;
public:
  Point(double x, double y);
  double x(void);
  double y(void);
};

class Polygon {
  std::vector<Point> __pts;
public:
  void add(Point pnt);
  Point& get(unsigned long idx);
  unsigned long size(void);
};

#endif

polygon.cpp

#include "polygon.h"

Point::Point(double x, double y) : __x(x), __y(y) {
}

double Point::x(void) {
    return __x;
}

double Point::y(void) {
    return __y;
}

void Polygon::add(Point pnt) {
    __pts.push_back(pnt);
}

Point& Polygon::get(unsigned long idx) {
    return __pts.at(idx);
}

unsigned long Polygon::size(void) {
    return __pts.size();
}

config.m4

PHP_ARG_ENABLE(geometry,
    [Whether to enable the "geometry" extension],
    [  --enable-geometry      Enable "geometry" extension support])

if test $PHP_GEOMETRY != "no"; then
    PHP_REQUIRE_CXX()
    PHP_SUBST(GEOMETRY_SHARED_LIBADD)
    PHP_ADD_LIBRARY(stdc++, 1, GEOMETRY_SHARED_LIBADD)
    PHP_NEW_EXTENSION(geometry, geometry.cpp polygon.cpp, $ext_shared)
fi

php_geometry.h

#ifndef PHP_GEOMETRY_H
#define PHP_GEOMETRY_H 1

#define PHP_GEOMETRY_EXTNAME  "geometry"
#define PHP_GEOMETRY_EXTVER   "0.1"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

extern "C" {
#include "php.h"
}

extern zend_module_entry geometry_module_entry;
#define phpext_geometry_ptr &geometry_module_entry;

PHP_METHOD(Point, __construct);
PHP_METHOD(Point, x);
PHP_METHOD(Point, y);

PHP_METHOD(Polygon, __construct);
PHP_METHOD(Polygon, add);
PHP_METHOD(Polygon, get);
PHP_METHOD(Polygon, size);

#endif

geometry.cpp

#include "php_geometry.h"

#include "polygon.h"

zend_object_handlers point_object_handlers;
zend_object_handlers polygon_object_handlers;

struct point_object {
  zend_object std;
  Point* point;
};

struct polygon_object {
  zend_object std;
  Polygon* polygon;
};

zend_class_entry* point_ce;
zend_class_entry* polygon_ce;

void point_free_storage(void* object TSRMLS_DC) {
    point_object* obj = (point_object*)object;
    delete obj->point;

    zend_hash_destroy(obj->std.properties);
    FREE_HASHTABLE(obj->std.properties);

    efree(obj);
}

void polygon_free_storage(void* object TSRMLS_DC) {
    polygon_object* obj = (polygon_object*)object;
    delete obj->polygon;

    zend_hash_destroy(obj->std.properties);
    FREE_HASHTABLE(obj->std.properties);

    efree(obj);
}

zend_object_value point_create_handler(zend_class_entry* type TSRMLS_DC) {
    zval* tmp;
    zend_object_value retval;

    point_object* obj = (point_object*)emalloc(sizeof(point_object));
    memset(obj, 0, sizeof(point_object));
    obj->std.ce = type;

    ALLOC_HASHTABLE(obj->std.properties);
    zend_hash_init(obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
    zend_hash_copy(obj->std.properties, &type->default_properties, (copy_ctor_func_t)zval_add_ref, (void*)&tmp, sizeof(zval*));

    retval.handle = zend_objects_store_put(obj, NULL,
        point_free_storage, NULL TSRMLS_CC);
    retval.handlers = &point_object_handlers;

    return retval;
}

zend_object_value polygon_create_handler(zend_class_entry* type TSRMLS_DC) {
    zval* tmp;
    zend_object_value retval;

    polygon_object* obj = (polygon_object*)emalloc(sizeof(polygon_object));
    memset(obj, 0, sizeof(polygon_object));
    obj->std.ce = type;

    ALLOC_HASHTABLE(obj->std.properties);
    zend_hash_init(obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
    zend_hash_copy(obj->std.properties, &type->default_properties, (copy_ctor_func_t)zval_add_ref, (void*)&tmp, sizeof(zval*));

    retval.handle = zend_objects_store_put(obj, NULL,
        polygon_free_storage, NULL TSRMLS_CC);
    retval.handlers = &polygon_object_handlers;

    return retval;
}

function_entry point_methods[] = {
    PHP_ME(Point, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(Point, x,           NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Point, y,           NULL, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

function_entry polygon_methods[] = {
    PHP_ME(Polygon, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(Polygon, add,         NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Polygon, get,         NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Polygon, size,        NULL, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

PHP_MINIT_FUNCTION(geometry) {
    zend_class_entry ce;

    INIT_CLASS_ENTRY(ce, "Point", point_methods);
    point_ce = zend_register_internal_class(&ce TSRMLS_CC);
    point_ce->create_object = point_create_handler;
    memcpy(&point_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
    point_object_handlers.clone_obj = NULL;

    INIT_CLASS_ENTRY(ce, "Polygon", polygon_methods);
    polygon_ce = zend_register_internal_class(&ce TSRMLS_CC);
    polygon_ce->create_object = polygon_create_handler;
    memcpy(&polygon_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
    polygon_object_handlers.clone_obj = NULL;

    return SUCCESS;
}

zend_module_entry geometry_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_GEOMETRY_EXTNAME,
    NULL,        /* Functions */
    PHP_MINIT(geometry),        /* MINIT */
    NULL,        /* MSHUTDOWN */
    NULL,        /* RINIT */
    NULL,        /* RSHUTDOWN */
    NULL,        /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_GEOMETRY_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_GEOMETRY
extern "C" {
ZEND_GET_MODULE(geometry)
}
#endif

PHP_METHOD(Point, __construct) {
    double x = .0;
    double y = .0;
    zval *object = getThis();

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|dd", &x, &y) == FAILURE)
        RETURN_NULL();

    point_object *obj = (point_object *)zend_object_store_get_object(object TSRMLS_CC);
    obj->point = new Point(x, y);
}

PHP_METHOD(Point, x) {
    point_object *obj = (point_object *)zend_object_store_get_object(getThis() TSRMLS_CC);
    Point *point = obj->point;

    if(point != NULL)
        RETURN_DOUBLE(point->x());
}

PHP_METHOD(Point, y) {
    point_object *obj = (point_object *)zend_object_store_get_object(getThis() TSRMLS_CC);
    Point *point = obj->point;

    if(point != NULL)
        RETURN_DOUBLE(point->y());
}

PHP_METHOD(Polygon, __construct) {
    zval* object = getThis();
    polygon_object* obj = (polygon_object*)zend_object_store_get_object(object TSRMLS_CC);
    obj->polygon = new Polygon;
}

PHP_METHOD(Polygon, add) {
    polygon_object *obj = (polygon_object*)zend_object_store_get_object(getThis() TSRMLS_CC);
    Polygon *polygon = obj->polygon;

    if(polygon != NULL) {
        zval* oth;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &oth, point_ce) == FAILURE)
            RETURN_NULL();

        point_object* ooth = (point_object*)zend_object_store_get_object(oth TSRMLS_CC);

        polygon->add(*ooth->point);
    }
}

PHP_METHOD(Polygon, get) {
    polygon_object *obj = (polygon_object*)zend_object_store_get_object(getThis() TSRMLS_CC);
    Polygon *polygon = obj->polygon;

    if(polygon != NULL) {
        long index;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE)
            RETURN_NULL();

        if (object_init_ex(return_value, point_ce) != SUCCESS) {
        } else {
            struct point_object* vobj = (struct point_object *) zend_object_store_get_object(return_value TSRMLS_CC);
            assert (vobj != NULL);
            vobj->point = &polygon->get(index);
        }
    }
}

PHP_METHOD(Polygon, size) {
    polygon_object *obj = (polygon_object*)zend_object_store_get_object(getThis() TSRMLS_CC);
    Polygon *polygon = obj->polygon;

    if(polygon != NULL)
        RETURN_LONG(polygon->size());
}

Then, after compiling I try the following PHP code:

<?php
echo '<pre>';

$pt = new Point;

var_dump($pt);

$pl = new Polygon;
$pl->add($pt);
$pl->add(new Point(10, 0));
$pl->add(new Point(10, 10));

for($i = 0; $i < $pl->size(); $i++)
    var_dump($pl->get($i));
?>

Always crashes when executing the Polygon::get method, if I comment it, nothing wrong happens.

6
  • +1 because I hardly go this way under the hood. Especially if I am using such an high-level language like PHP Commented Jun 14, 2011 at 18:59
  • @Wanderson - As you said, it crashes at Polygon::get member function, just check whether member functions argument is within the size of the vector. Probably you are accessing the vector out of it's bounds. return __pts.at(idx); idx should be range from 0 to vector size-1. Keep the break point at the return statement and see what is the value of the argument. Commented Jun 14, 2011 at 18:59
  • @Mahesh - It is just a code sample, I'm making sure I'm not out of the range, if you take for loop out and then put $pl->get(0) (the first element it still fails. Commented Jun 14, 2011 at 19:09
  • Now, I'm trying to use a Point object pointer vector. Now PHP does not chash, but when I try to retrieve X ou Y, it returns NULL. Commented Jun 14, 2011 at 19:10
  • @Wanderson - In that case, comment the return in get method and see what is the size of the vector _pts with a cout statement. Also for testing purpose change the member function return type to void. ( This is for your previous comment ) Commented Jun 14, 2011 at 19:12

1 Answer 1

2

Valgrind gives me this:

==14188== Invalid free() / delete / delete[]
==14188==    at 0x4C27A83: operator delete(void*) (vg_replace_malloc.c:387)
==14188==    by 0xE1C3D7C: point_free_storage(void*) (geometry.cpp:24)

So, at some point, this line:

delete obj->point;

... is executed while obj->point is not an actual pointer. Removing the delete call will most probably remove the error as well (although a leak will most likely occur, because obj->point will be a pointer in some instances).

From what I can see, Polygon::get does not return a pointer, it returns a reference. Since it's not a pointer, calling delete on it will give you an Invalid free() error.

Sign up to request clarification or add additional context in comments.

4 Comments

In order to try to solve, I've converted ths Polygon's Point vector to Point* vector, then get return value is now Point*.
@Wanderson: I was going to edit my answer to say exactly that. :)
@Mahesh - Other detail that I saw is that once a Point* is associated to a zval*, PHP crashes too when I try to associate the same Point* to another zval*, but then, how can I retrieve a Point* that was previously associated with a zval*?
@Wanderson: zend_object_store_get_object? Not sure what you mean, you have an example?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.