I've been rewriting my embedded Python extension for PHP5 so that I
can take advantage of ZE2's spiffy object handler system. I've just
gotten most things working, but I noticed that my custom object
destructor is never being executed.
I ran the following through the debugger a few times:
$p = new Python('foo', 'Test');
And compared it to the execution of a "native" PHP class:
class Foo {}
$f = new Foo();
The difference appears to lay with the reference counts on the
resultant store object. With my Python class, an additional reference
is added to the store object, so zend_objects_store_del_ref() never
drops the reference count to zero, and the destructor is never
invoked.
It looks like this code from zend_execute.c is responsible for the
added reference:
if (PZVAL_IS_REF(value) && value->refcount > 0) {
ALLOC_ZVAL(variable_ptr);
*variable_ptr_ptr = variable_ptr;
*variable_ptr = *value;
zval_copy_ctor(variable_ptr);
variable_ptr->refcount=1;
break;
}
In my case, 'value' is a reference (PZVAL_IS_REF(value) == 1), and in
the case of the native PHP object, it isn't. zval_copy_ctor() calls
zend_objects_store_add_ref(), which increments the store object's
reference count.
Two questions:
Why is my object being treated as a reference while the native PHP
object is not?
'variable_ptr->refcount=1;' resets the reference count of the
variable's zval to 1, but the underlying _store_object's reference
count is still incremented. What will ever reclaim that reference on
the _store_object?
I'm still finding my way around the the "new" ZE2 way of doing things,
so please be gentle if the answers to these questions is obvious.
--
Jon Parise (jon@php.net) :: The PHP Project (http://www.php.net/)
It looks like this code from zend_execute.c is responsible for the
added reference:if (PZVAL_IS_REF(value) && value->refcount > 0) { ALLOC_ZVAL(variable_ptr); *variable_ptr_ptr = variable_ptr; *variable_ptr = *value; zval_copy_ctor(variable_ptr); variable_ptr->refcount=1; break; }
In my case, 'value' is a reference (PZVAL_IS_REF(value) == 1), and in
the case of the native PHP object, it isn't. zval_copy_ctor() calls
zend_objects_store_add_ref(), which increments the store object's
reference count.Two questions:
Why is my object being treated as a reference while the native PHP
object is not?
I guess that is a question for you really: are you doing anything
special to make your object a reference? I've used the new object model
for PHP-GTK and my objects were not treated as references.
'variable_ptr->refcount=1;' resets the reference count of the
variable's zval to 1, but the underlying _store_object's reference
count is still incremented. What will ever reclaim that reference on
the _store_object?
zval_dtor() will. It calls del_ref on the underlying object.
- Andrei
Why is my object being treated as a reference while the native PHP
object is not?I guess that is a question for you really: are you doing anything
special to make your object a reference? I've used the new object model
for PHP-GTK and my objects were not treated as references.
It appears to be a side-effect of not defining the 'scope' field in
the zend_internal_function call that is invokved from my custom
'constructor_get' handler. If the 'scope' field is NULL, my object
is treated as a reference. If I assign my class entry to the 'scope'
field, the object is "normal" (i.e. not a reference).
'variable_ptr->refcount=1;' resets the reference count of the
variable's zval to 1, but the underlying _store_object's reference
count is still incremented. What will ever reclaim that reference on
the _store_object?zval_dtor() will. It calls del_ref on the underlying object.
I see. I would have to call zval_dtor() myself in any of the cases
where I knowingly created an additional reference. That makes sense.
--
Jon Parise (jon@php.net) :: The PHP Project (http://www.php.net/)
It appears to be a side-effect of not defining the 'scope' field in
the zend_internal_function call that is invokved from my custom
'constructor_get' handler. If the 'scope' field is NULL, my object
is treated as a reference. If I assign my class entry to the 'scope'
field, the object is "normal" (i.e. not a reference).
Hmm, can I see the code?
zval_dtor() will. It calls del_ref on the underlying object.
I see. I would have to call zval_dtor() myself in any of the cases
where I knowingly created an additional reference. That makes sense.
Ditto. :)
- Andrei
It appears to be a side-effect of not defining the 'scope' field in
the zend_internal_function call that is invokved from my custom
'constructor_get' handler. If the 'scope' field is NULL, my object
is treated as a reference. If I assign my class entry to the 'scope'
field, the object is "normal" (i.e. not a reference).Hmm, can I see the code?
Here's my constructor function definition:
zend_internal_function php_python_constructor_function = {
ZEND_INTERNAL_FUNCTION, /* type */
"python", /* function_name */
&python_class_entry, /* scope */
0, /* fn_flags */
NULL, /* prototype */
0, /* num_args */
NULL, /* arg_info */
0, /* pass_rest_by_reference */
ZEND_FN(python_new) /* handler */
};
I use this function as part of my class initialization:
INIT_CLASS_ENTRY(python_class_entry, "python", NULL);
python_class_entry.create_object = python_object_create;
python_class_entry.constructor = (union _zend_function *)&php_python_constructor_function;
zend_register_internal_class(&python_class_entry TSRMLS_CC);
And I return the 'constructor' value from my 'constructor_get'
handler:
static union _zend_function *
python_constructor_get(zval *object TSRMLS_DC)
{
php_python_object *obj = PIP_FETCH(object);
return obj->ce->constructor;
}
If I specify NULL
for the 'scope' field, I end up with a second
reference to the new object.
I don't know if that's intentional or a bug. I'm still getting
familiar with the new object model code.
--
Jon Parise (jon@php.net) :: The PHP Project (http://www.php.net/)
Hello Jon,
Saturday, January 3, 2004, 2:21:12 AM, you wrote:
It appears to be a side-effect of not defining the 'scope' field in
the zend_internal_function call that is invokved from my custom
'constructor_get' handler. If the 'scope' field is NULL, my object
is treated as a reference. If I assign my class entry to the 'scope'
field, the object is "normal" (i.e. not a reference).Hmm, can I see the code?
Here's my constructor function definition:
zend_internal_function php_python_constructor_function = { ZEND_INTERNAL_FUNCTION, /* type */ "python", /* function_name */ &python_class_entry, /* scope */ 0, /* fn_flags */ NULL, /* prototype */ 0, /* num_args */ NULL, /* arg_info */ 0, /* pass_rest_by_reference */ ZEND_FN(python_new) /* handler */ };
I use this function as part of my class initialization:
INIT_CLASS_ENTRY(python_class_entry, "python", NULL); python_class_entry.create_object = python_object_create; python_class_entry.constructor = (union _zend_function *)&php_python_constructor_function; zend_register_internal_class(&python_class_entry TSRMLS_CC);
And I return the 'constructor' value from my 'constructor_get'
handler:
static union _zend_function * python_constructor_get(zval *object TSRMLS_DC) { php_python_object *obj = PIP_FETCH(object);
return obj->ce->constructor; }
If I specify
NULL
for the 'scope' field, I end up with a second
reference to the new object.
I don't know if that's intentional or a bug. I'm still getting
familiar with the new object model code.
Why do you make it so complicated?
You could imply provide a list of methods for your objects and register
them with the third parameter of INIT_CLASS_ENTRY() macro. That would handle
the constructor correct. Also you should name the constructor '__construct'
instead of 'python' because the former is the prefered way for ZE2.
--
Best regards,
Marcus mailto:helly@php.net
Why do you make it so complicated? You could imply provide a list
of methods for your objects and register them with the third
parameter of INIT_CLASS_ENTRY() macro.
I want the list of available methods to be discovered at runtime by
inspecting the underlying Python object.
In terms of it being complicated, I'll cite the lack of documentation
and examples. For the most part, I've been working with the existing
com_dotnet and java extensions as inspiration, along with studying
zend_object_handlers.[ch].
Also you should name the constructor '__construct' instead of
'python' because the former is the prefered way for ZE2.
That was a remnant of my original (Zend Engine 1) code (where the
constructor had to match the name of the container class). Thanks for
noticing it. I've made the change.
--
Jon Parise (jon@php.net) :: The PHP Project (http://www.php.net/)