Custom object creation and reference counts

php.internals

Jon Parise

22 years ago
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/)

Andrei Zmievski

22 years ago
On Fri, 02 Jan 2004, Jon Parise wrote:
> 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

Jon Parise

22 years ago
On Fri, Jan 02, 2004 at 04:09:21PM -0800, Andrei Zmievski wrote:
> > 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/)

Andrei Zmievski

22 years ago
On Fri, 02 Jan 2004, Jon Parise 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?
> > 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

Jon Parise

22 years ago
On Fri, Jan 02, 2004 at 05:00:33PM -0800, Andrei Zmievski 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.
-- Jon Parise (jon@php.net) :: The PHP Project (http://www.php.net/)

Marcus Börger

22 years ago
Hello Jon, Saturday, January 3, 2004, 2:21:12 AM, you wrote:
> On Fri, Jan 02, 2004 at 05:00:33PM -0800, Andrei Zmievski 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

Jon Parise

22 years ago
On Sat, Jan 03, 2004 at 10:14:55AM +0100, Marcus Boerger wrote:
> 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/)