From ffdaa0590d33ea89d116f6c56df2474cc0675ec9 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 25 Sep 2013 14:59:58 -0700 Subject: {auto} PHP objects can now be implemented in C++. Constructors and destructors get called at the appropriate time, but not yet any of the other methods --- include/base.h | 19 +++++ include/classinfo.h | 45 +++++++++-- include/extension.h | 4 +- include/function.h | 68 ++++++++-------- include/hiddenpointer.h | 30 +++---- src/classinfo.cpp | 153 +++++++++++++++++++++++++++++------ src/extension.cpp | 8 +- src/function.cpp | 40 +++++----- src/internalfunction.h | 11 +-- src/nativefunction.h | 4 +- src/value.cpp | 206 ++++++++++++++++++++++++------------------------ tests/simple/simple.cpp | 30 ++++--- tests/simple/simple.php | 13 +-- 13 files changed, 393 insertions(+), 238 deletions(-) diff --git a/include/base.h b/include/base.h index eceb114..797aff6 100644 --- a/include/base.h +++ b/include/base.h @@ -16,6 +16,11 @@ namespace Php { class Base { public: + /** + * Virtual destructor + */ + virtual ~Base() {} + /** * The pseudo constructor that is called from PHP after the object is constructed * @param environment @@ -46,6 +51,20 @@ public: */ virtual void __construct() {} + /** + * The pseudo destructor that is called from PHP right before the object is destructed + * @param environment + */ + virtual void __destruct(Environment &environment) + { + // call the other implementation + __destruct(); + } + + /** + * The pseudo destructor that is called from PHP right before the object is destructed + */ + virtual void __destruct() {} }; diff --git a/include/classinfo.h b/include/classinfo.h index 3941259..fb92ce4 100644 --- a/include/classinfo.h +++ b/include/classinfo.h @@ -21,6 +21,11 @@ struct _zend_class_entry; */ namespace Php { +/** + * Forward declarations + */ +class InternalFunction; + /** * Virtual base class of the classInfo * @@ -34,31 +39,49 @@ public: * Constructor * @param name */ - _ClassInfo(const char *name) : _name(name), _entry(NULL) {} + _ClassInfo(const char *name); /** * Destructor */ - virtual ~_ClassInfo() {} + virtual ~_ClassInfo(); /** * Initialize the class */ void initialize(); - -private: + /** - * Class name - * @var string + * Construct the C++ object + * @return Base */ - std::string _name; + virtual Base *construct() = 0; +private: /** * The class entry * @var zend_class_entry */ struct _zend_class_entry *_entry; + /** + * The name + * @var string + */ + std::string _name; + + /** + * Constructor function + * @var InternalFunction + */ + InternalFunction *_constructor; + + /** + * Destructor function + * @var InternalFunction + */ + InternalFunction *_destructor; + }; /** @@ -82,6 +105,14 @@ public: */ virtual ~ClassInfo() {} + /** + * Construct the object + * @return Base + */ + virtual Base *construct() + { + return _type.construct(); + } private: /** diff --git a/include/extension.h b/include/extension.h index 1984aa9..bf945f7 100644 --- a/include/extension.h +++ b/include/extension.h @@ -173,7 +173,7 @@ public: * It is only possible to create functions during the initialization of * the library, before the Extension::module() method is called. * - * Note that the function must have been allocated on the HEAP (using + * Note that the function must have been allocated on the HEAP (using * "new") and that the object will be destructed (using "delete") * by the extension object (you thus do not have to destruct it * yourself!) @@ -187,7 +187,7 @@ public: * Add a native function directly to the extension * @param name Name of the function * @param function The function to add - * @param arguments Optional argument specification + * @param arguments Optional argument specification * @return Function The added function */ Function *add(const char *name, native_callback_0 function, const std::initializer_list &arguments = {}); diff --git a/include/function.h b/include/function.h index c78fdd6..89ec3fd 100644 --- a/include/function.h +++ b/include/function.h @@ -28,7 +28,7 @@ class Function public: /** * Constructor - * @param name Name of the function + * @param name Name of the function * @param min Min number of arguments * @param max Max number of arguments */ @@ -55,42 +55,42 @@ public: /** * Comparison - * @param function The other function - * @return bool + * @param function The other function + * @return bool */ bool operator<(const Function &function) const { - return strcmp(name(), function.name()) < 0; - } + return strcmp(name(), function.name()) < 0; + } /** * Comparison - * @param function The other function - * @return bool + * @param function The other function + * @return bool */ bool operator>(const Function &function) const { - return strcmp(name(), function.name()) > 0; - } + return strcmp(name(), function.name()) > 0; + } /** * Comparison - * @param function The other function - * @return bool + * @param function The other function + * @return bool */ bool operator==(const Function &function) const { - return strcmp(name(), function.name()) == 0; - } + return strcmp(name(), function.name()) == 0; + } - /** - * Function name - * @return const char * - */ - const char *name() const - { - return _ptr.text(); - } + /** + * Function name + * @return const char * + */ + const char *name() const + { + return _ptr.text(); + } /** * Method that gets called every time the function is executed @@ -111,17 +111,17 @@ protected: */ Type _type = nullType; - /** - * Required number of arguments - * @var integer - */ - int _required; - - /** - * Total number of arguments - * @var integer - */ - int _argc; + /** + * Required number of arguments + * @var integer + */ + int _required; + + /** + * Total number of arguments + * @var integer + */ + int _argc; /** * The arguments @@ -130,8 +130,8 @@ protected: struct _zend_arg_info *_argv; /** - * The name is stored in a hidden pointer, so that we have access to the function - * @var HiddenPointer + * The object address is stored in a hidden pointer, so that we have access to the function object + * @var HiddenPointer */ HiddenPointer _ptr; diff --git a/include/hiddenpointer.h b/include/hiddenpointer.h index 3502bc9..f432f65 100644 --- a/include/hiddenpointer.h +++ b/include/hiddenpointer.h @@ -113,19 +113,19 @@ public: /** * Change the pointer - * @param Type* + * @param Type* */ void setPointer(Type *pointer) { - // store pointer - _pointer = pointer; + // store pointer + _pointer = pointer; - // overwrite in data - _data.replace(0, sizeof(Type *), (const char *)&_pointer, sizeof(Type *)); - + // overwrite in data + _data.replace(0, sizeof(Type *), (const char *)&_pointer, sizeof(Type *)); + // for safety reasons, we recalculate text pointer _text = _data.c_str() + sizeof(Type *); - } + } /** * Retrieve the text @@ -138,14 +138,14 @@ public: /** * Change the text - * @param text - * @param size + * @param text + * @param size */ void setText(const char *text, int size=-1) { - // check if size was set - if (size < 0) size = strlen(text); - + // check if size was set + if (size < 0) size = strlen(text); + // reserve enough room for the text and the pointer _data.reserve(size + sizeof(Type *)); @@ -157,7 +157,7 @@ public: // store new text _text = _data.c_str() + sizeof(Type *); - } + } /** * Cast to the pointer @@ -183,8 +183,8 @@ public: */ int length() const { - return _data.size() - sizeof(Type *); - } + return _data.size() - sizeof(Type *); + } private: /** diff --git a/src/classinfo.cpp b/src/classinfo.cpp index 760df5c..7de7522 100644 --- a/src/classinfo.cpp +++ b/src/classinfo.cpp @@ -13,41 +13,147 @@ namespace Php { /** - * Function that is called by the Zend engine every time that a function gets called + * Structure that combines a C++ object with a zend object + */ +struct MixedObject +{ + zend_object php; + Base *cpp; +}; + +/** + * Function that is called to clean up space that is occupied by the object + * @param object The object to be deallocated + */ +static void deallocate_object(void *object TSRMLS_DC) +{ + // allocate memory for the object + MixedObject *obj = (MixedObject *)object; + + // deallocate the cpp object + if (obj->cpp) delete obj->cpp; + + // get rid of the object properties + zend_hash_destroy(obj->php.properties); + FREE_HASHTABLE(obj->php.properties); + + // deallocate the entire object + efree(obj); +} + +/** + * Function that is called to create space for a cloned object + * @param object The object to be cloned + * @param clone The address that should become the clone + */ +static void clone_object(void *object, void **clone TSRMLS_DC) +{ + std::cout << "clone_object" << std::endl; + + // @todo implementation +} + +/** + * Function that is called when an instance of the class needs to be created. + * This function will create the C++ class, and the PHP object + * @param type Pointer to the class + */ +static zend_object_value create_object(zend_class_entry *type TSRMLS_DC) +{ + // allocate memory for the object + MixedObject *object = (MixedObject *)emalloc(sizeof(MixedObject)); + + // retrieve the classinfo object + _ClassInfo *info = (_ClassInfo *)type->info.user.doc_comment; + + // construct the cpp object + object->cpp = info->construct(); + + // store the class + object->php.ce = type; + + // the original create_object fills the initial object with the default properties, + // we're going to do exactly the same. start with setting up a hashtable for the props + ALLOC_HASHTABLE(object->php.properties); + + // initialize the hash table + zend_hash_init(object->php.properties, 0, NULL, ZVAL_PTR_DTOR, 0); + + // initialize the properties + object_properties_init(&(object->php), type); + + // the thing we're going to return + zend_object_value result; + + // use default object handlers + result.handlers = zend_get_std_object_handlers(); + + // put the object in the storage, and assign a method for deallocating and cloning + result.handle = zend_objects_store_put(object, NULL, deallocate_object, clone_object TSRMLS_CC); + + // done + return result; +} + +/** + * Function that is called by the Zend engine every time the constructor gets called * @param ht * @param return_value * @param return_value_ptr * @param this_ptr * @param return_value_used * @param tsrm_ls - * @return integer */ -void invoke_method(INTERNAL_FUNCTION_PARAMETERS) +static void invoke_constructor(INTERNAL_FUNCTION_PARAMETERS) { - std::cout << "invoke method" << std::endl; - - return; - - // find the function name - const char *name = get_active_function_name(TSRMLS_C); - - // uncover the hidden pointer inside the function name - Function *function = HiddenPointer(name); - - // wrap the return value - Value result(return_value, true); + // get the mixed object + MixedObject *obj = (MixedObject *)zend_object_store_get_object(this_ptr TSRMLS_CC); // construct parameters Parameters params(ZEND_NUM_ARGS()); - // get the result - result = function->invoke(*PHPCPP_G(environment), params); + // call the constructor + obj->cpp->__construct(*PHPCPP_G(environment), params); +} + +/** + * Function that is called by the Zend engine every time the destructor gets called + * @param ht + * @param return_value + * @param return_value_ptr + * @param this_ptr + * @param return_value_used + * @param tsrm_ls + */ +static void invoke_destructor(INTERNAL_FUNCTION_PARAMETERS) +{ + // get the mixed object + MixedObject *obj = (MixedObject *)zend_object_store_get_object(this_ptr TSRMLS_CC); + + // call the destructor + obj->cpp->__destruct(*PHPCPP_G(environment)); } /** - * Helper struct to create an internal method + * Constructor + * @param name */ +_ClassInfo::_ClassInfo(const char *name) : _name(name), _entry(NULL) +{ + // allocate internal functions + _constructor = new InternalFunction(invoke_constructor, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR); + _destructor = new InternalFunction(invoke_destructor, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR); +} +/** + * Destructor + */ +_ClassInfo::~_ClassInfo() +{ + // deallocate internal function + delete _constructor; + delete _destructor; +} /** * Initialize the class @@ -61,15 +167,16 @@ void _ClassInfo::initialize(TSRMLS_D) // initialize the class entry INIT_CLASS_ENTRY_EX(entry, _name.c_str(), _name.size(), NULL); - // functions we need - // @todo should not be static - static InternalFunction constructor(invoke_method, ZEND_ACC_CTOR|ZEND_ACC_PUBLIC); - // we need a special constructor - entry.__call = constructor; + entry.create_object = create_object; + entry.constructor = _constructor->function(); + entry.destructor = _destructor->function(); // register the class _entry = zend_register_internal_class(&entry TSRMLS_CC); + + // store pointer to the class in the unused doc_comment member + _entry->info.user.doc_comment = (const char *)this; } /** diff --git a/src/extension.cpp b/src/extension.cpp index 7f6c9dd..f4cc9ce 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -21,7 +21,7 @@ namespace Php { /** * Pointer to the one and only extension object - * @var Extension + * @var Extension */ static Extension *extension = nullptr; @@ -123,9 +123,9 @@ static int request_shutdown(INIT_FUNC_ARGS) */ Extension::Extension(const char *name, const char *version, request_callback start, request_callback stop) : _start(start), _stop(stop) { - // store extension variable - extension = this; - + // store extension variable + extension = this; + // allocate memory (we allocate this on the heap so that the size of the // entry does not have to be defined in the .h file. We pay a performance // price for this, but we pay this price becuase the design goal of the diff --git a/src/function.cpp b/src/function.cpp index 2afc1be..79117e5 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -37,34 +37,34 @@ void invoke_function(INTERNAL_FUNCTION_PARAMETERS) // construct parameters Parameters params(ZEND_NUM_ARGS()); - // get the result - result = function->invoke(*PHPCPP_G(environment), params); + // get the result + result = function->invoke(*PHPCPP_G(environment), params); } /** * Constructor - * @param name Name of the function + * @param name Name of the function * @param min Min number of arguments * @param max Max number of arguments */ Function::Function(const char *name, const std::initializer_list &arguments) : _ptr(this, name) { - // construct vector for arguments - _argc = arguments.size(); - _argv = new zend_arg_info[_argc+1]; - - // counter - int i=1; - - // loop through the arguments - for (auto it = arguments.begin(); it != arguments.end(); it++) - { - // fill the arg info - it->fill(&_argv[i++]); - } - - // @todo find out number of required arguments - _required = _argc; + // construct vector for arguments + _argc = arguments.size(); + _argv = new zend_arg_info[_argc+1]; + + // counter + int i=1; + + // loop through the arguments + for (auto it = arguments.begin(); it != arguments.end(); it++) + { + // fill the arg info + it->fill(&_argv[i++]); + } + + // @todo find out number of required arguments + _required = _argc; } /** @@ -72,7 +72,7 @@ Function::Function(const char *name, const std::initializer_list &argu */ Function::~Function() { - delete[] _argv; + delete[] _argv; } /** diff --git a/src/internalfunction.h b/src/internalfunction.h index 741ea5b..31abcca 100644 --- a/src/internalfunction.h +++ b/src/internalfunction.h @@ -46,20 +46,11 @@ public: */ virtual ~InternalFunction() {} - /** - * Cast to zend_internal_function pointer - * @return zend_internal_function - */ - operator zend_internal_function *() - { - return &_func; - } - /** * Cast to zend_function pointer * @return zend_function */ - operator zend_function *() + zend_function *function() { return (zend_function *)&_func; } diff --git a/src/nativefunction.h b/src/nativefunction.h index 88d629f..2a61ccb 100644 --- a/src/nativefunction.h +++ b/src/nativefunction.h @@ -21,8 +21,8 @@ class NativeFunction : public Function public: /** * Constructor - * @param name Function name - * @param function The native C function + * @param name Function name + * @param function The native C function */ NativeFunction(const char *name, native_callback_0 function, const std::initializer_list &arguments = {}) : Function(name, arguments), _type(0) { _function.f0 = function; } NativeFunction(const char *name, native_callback_1 function, const std::initializer_list &arguments = {}) : Function(name, arguments), _type(1) { _function.f1 = function; } diff --git a/src/value.cpp b/src/value.cpp index 5239b33..2a571ab 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -144,9 +144,9 @@ Value::Value(struct _zval_struct *val, bool ref) // we're going to make will not change the other variable if (ref && Z_REFCOUNT_P(_val) > 1) { - // separate the zval - SEPARATE_ZVAL_IF_NOT_REF(&_val); - } + // separate the zval + SEPARATE_ZVAL_IF_NOT_REF(&_val); + } // we see ourselves as reference too Z_ADDREF_P(_val); @@ -216,40 +216,40 @@ Value &Value::operator=(const Value &value) // skip self assignment if (this == &value) return *this; - // is the object a reference? - if (Z_ISREF_P(_val)) - { - // the current object is a reference, this means that we should - // keep the zval object, and copy the other value into it, get - // the current refcount - int refcount = Z_REFCOUNT_P(_val); - - // clean up the current zval (but keep the zval structure) - zval_dtor(_val); - - // make the copy - *_val = *value._val; - zval_copy_ctor(_val); - - // restore refcount and reference setting - Z_SET_ISREF_TO_P(_val, true); - Z_SET_REFCOUNT_P(_val, refcount); - } - else - { - // destruct the zval (this function will decrement the reference counter, - // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); - - // just copy the zval, and the refcounter - _val = value._val; - - // and we have one more reference - Z_ADDREF_P(_val); - } - - // update the object - return *this; + // is the object a reference? + if (Z_ISREF_P(_val)) + { + // the current object is a reference, this means that we should + // keep the zval object, and copy the other value into it, get + // the current refcount + int refcount = Z_REFCOUNT_P(_val); + + // clean up the current zval (but keep the zval structure) + zval_dtor(_val); + + // make the copy + *_val = *value._val; + zval_copy_ctor(_val); + + // restore refcount and reference setting + Z_SET_ISREF_TO_P(_val, true); + Z_SET_REFCOUNT_P(_val, refcount); + } + else + { + // destruct the zval (this function will decrement the reference counter, + // and only destruct if there are no other references left) + zval_ptr_dtor(&_val); + + // just copy the zval, and the refcounter + _val = value._val; + + // and we have one more reference + Z_ADDREF_P(_val); + } + + // update the object + return *this; } /** @@ -262,65 +262,65 @@ Value &Value::operator=(Value &&value) // skip self assignment if (this == &value) return *this; - // is the object a reference? - if (Z_ISREF_P(_val)) - { + // is the object a reference? + if (Z_ISREF_P(_val)) + { // @todo difference if the other object is a reference or not? - // the current object is a reference, this means that we should - // keep the zval object, and copy the other value into it, get - // the current refcount - int refcount = Z_REFCOUNT_P(_val); - - // clean up the current zval (but keep the zval structure) - zval_dtor(_val); - - // make the copy - *_val = *value._val; - - // restore reference and refcount setting - Z_SET_ISREF_TO_P(_val, true); - Z_SET_REFCOUNT_P(_val, refcount); + // the current object is a reference, this means that we should + // keep the zval object, and copy the other value into it, get + // the current refcount + int refcount = Z_REFCOUNT_P(_val); + + // clean up the current zval (but keep the zval structure) + zval_dtor(_val); + + // make the copy + *_val = *value._val; + + // restore reference and refcount setting + Z_SET_ISREF_TO_P(_val, true); + Z_SET_REFCOUNT_P(_val, refcount); // how many references did the old variable have? if (Z_ISREF_P(value._val) > 1) { - // the other object already had multiple references, this - // implies that many other PHP variables are also referring - // to it, and we still need to store its contents, with one - // reference less - Z_DELREF_P(value._val); - - // and we need to run the copy constructor on the current - // value, because we're making a deep copy - zval_copy_ctor(_val); - } - else - { - // the last and only reference to the other object was - // removed, we no longer need it - FREE_ZVAL(value._val); - - // the other object is no longer valid - value._val = nullptr; - } - } - else - { - // destruct the zval (this function will decrement the reference counter, - // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); - - // just copy the zval completely - _val = value._val; - - // the other object is no longer valid - value._val = nullptr; - } - - // update the object - return *this; + // the other object already had multiple references, this + // implies that many other PHP variables are also referring + // to it, and we still need to store its contents, with one + // reference less + Z_DELREF_P(value._val); + + // and we need to run the copy constructor on the current + // value, because we're making a deep copy + zval_copy_ctor(_val); + } + else + { + // the last and only reference to the other object was + // removed, we no longer need it + FREE_ZVAL(value._val); + + // the other object is no longer valid + value._val = nullptr; + } + } + else + { + // destruct the zval (this function will decrement the reference counter, + // and only destruct if there are no other references left) + zval_ptr_dtor(&_val); + + // just copy the zval completely + _val = value._val; + + // the other object is no longer valid + value._val = nullptr; + } + + // update the object + return *this; } /** @@ -339,8 +339,8 @@ Value &Value::operator=(int value) // set new value ZVAL_LONG(_val, value); - // update the object - return *this; + // update the object + return *this; } /** @@ -359,8 +359,8 @@ Value &Value::operator=(long value) // set new value ZVAL_LONG(_val, value); - // update the object - return *this; + // update the object + return *this; } /** @@ -379,8 +379,8 @@ Value &Value::operator=(bool value) // set new value ZVAL_BOOL(_val, value); - // update the object - return *this; + // update the object + return *this; } /** @@ -399,8 +399,8 @@ Value &Value::operator=(char value) // set new value ZVAL_STRINGL(_val, &value, 1, 1); - // update the object - return *this; + // update the object + return *this; } /** @@ -419,8 +419,8 @@ Value &Value::operator=(const std::string &value) // set new value ZVAL_STRINGL(_val, value.c_str(), value.size(), 1); - // update the object - return *this; + // update the object + return *this; } /** @@ -439,8 +439,8 @@ Value &Value::operator=(const char *value) // set new value ZVAL_STRING(_val, value, 1); - // update the object - return *this; + // update the object + return *this; } /** @@ -459,8 +459,8 @@ Value &Value::operator=(double value) // set new value ZVAL_DOUBLE(_val, value); - // update the object - return *this; + // update the object + return *this; } /** diff --git a/tests/simple/simple.cpp b/tests/simple/simple.cpp index 39e615a..e8558a0 100644 --- a/tests/simple/simple.cpp +++ b/tests/simple/simple.cpp @@ -51,26 +51,30 @@ private: public: MyCustomClass() { + _x = 3; + cout << "MyCustomClass::MyCustomClass" << endl; } - virtual void __construct() + virtual ~MyCustomClass() { + cout << "MyCustomClass::~MyCustomClass" << endl; + } + + virtual void __construct() + { + cout << "MyCustomClass::__construct" << endl; } virtual void __destruct() { - - + cout << "MyCustomClass::__destruct" << endl; } void myMethod(Php::Parameters ¶ms) { - - - } - + } }; // symbols are exported according to the "C" language @@ -84,12 +88,12 @@ extern "C" // define the functionsnm extension.add("my_plus", my_plus, { - Php::ByVal("a", Php::stringType), - Php::ByVal("b", Php::arrayType), - Php::ByVal("c", "MyClass"), - Php::ByRef("d", Php::stringType) - }); - + Php::ByVal("a", Php::stringType), + Php::ByVal("b", Php::arrayType), + Php::ByVal("c", "MyClass"), + Php::ByRef("d", Php::stringType) + }); + // define classes extension.add("my_class", Php::Class()); diff --git a/tests/simple/simple.php b/tests/simple/simple.php index 10cb603..07e3f78 100644 --- a/tests/simple/simple.php +++ b/tests/simple/simple.php @@ -12,10 +12,10 @@ $myvar = "hoi"; class MyClass { - public function __toString() - { - return "aksjdfhsdfkj"; - } + public function __toString() + { + return "aksjdfhsdfkj"; + } } $g1 = 123; @@ -35,7 +35,10 @@ echo("g3: $g3\n"); if (class_exists("my_class")) echo("Warempel, de class bestaat\n"); $x = new my_class(); +unset($x); + +echo("done\n"); -$x->my_method(); +//$x->my_method(); -- cgit v1.2.3