用C++扩展PHP - (2)

    技术2022-05-11  127

    6节.将C++类影射到PHP中

    目录

    MyClass

    宏、函数及其它

    封装代码

     

    PHP 5的类支持很多新的特性。如:权限(protected, public, private),异常,interfaces,等等。在这个简单的介绍中,我们只做最基本的事情:使PHP可以影射到C++的类。这样你可以用PHP中使用你的类,之后的事情将会变得很简单的。在看下面的介绍之前,你可以参考一下Sqlite, SimpleXML 及 cryptopp-php 模块的代码。

    这里介绍一下。我们要用一个C++类做为例子,就叫做MyClass吧。在PHP术语中,把它叫做一个resource(资源)。PHP常使用这类的东西,如数据库的连接就是resource,它也可能是一个指向实际resource的struct(结构)。在C++中,class 实际上是struct的一个近义词(struct默认为public,classe默认为private  --仅这个区别而已)。

    在结构化的接口中,我们会用类似以下的PHP代码来使用resources:

    $m = myclass_new();

    myclass_set_string($m, 'hello world!');

    var_dump(myclass_get_string($m));

    ?>

    在面向对象式的接口中,一样可以使用PHP resources,不过已经被封装在一个PHP对象中了,如:

    $m = new MyClass;

    $m->setString('hello world!');

    var_dump($m->getString());

    ?>

    我们不需要关心被封装的实际的代码做了些什么。当我们有一个叫MyClass的C++类。我们可以把这个C++类当成resources并把C++类里的方法封装成PHP的函数。然后我们也可以把它封装成一个PHP的对象,使得可以像一般的C++那样使用。

    在看本文时,记住我们的目的就是:把C++类封装成PHP可以使用的结构化的函数或对象化的类。也许在一开始有些东西会令你迷惑,但读下去后就会慢慢明白的了。中间会有很多的宏定义,但当你看明白后,会觉得所有东西都很清淅很容易了。

    1  MyClass

    首先我们需要一个类。下边是一个只有一个私有属性和几个公有方法的简单的类。

    以下是这个类的声明头文件 myclass.h

    #ifndef __MYCLASS_H__

    #define __MYCLASS_H__

     

    #include

     

    using namespace std;

     

    class MyClass {

        private:

            string itsString;

     

        public:

            MyClass(string s = "default");

            ~MyClass();

     

            string getString() const;

            bool setString(const string s);

    };

     

    #endif

    下边是定义代码myclass.cpp

    #include "myclass.h"

     

    MyClass::MyClass(string s)

    {

       itsString = s;

    }

     

    MyClass::~MyClass()

    {

    }

     

    string MyClass::getString() const

    {

        return itsString;

    }

     

    bool MyClass::setString(const string s)

    {

        itsString = s;

        return true;

    }

    这只是一个做为例子的类。

    然后我们要让构建系统知道和可以编译这些文件。把config.m4文件做以下修改:

    PHP_NEW_EXTENSION(php5cpp, php5cpp.cpp, $ext_shared)

     

    becomes...

     

    PHP_NEW_EXTENSION(php5cpp, php5cpp.cpp myclass.cpp, $ext_shared)

    php5cpp.cpp 文件中增加#include “myclass.h”

    extern "C" {

    #include "php.h"

    #include "php_ini.h"

    #include "ext/standard/info.h"

    }

     

    #include "php_php5cpp.h"

    #include "myclass.h"

    不要把include php_php5cpp.hmyclass.h 的语句放在 extern "C" 中,否则会出现错误。

    2  宏、函数及其它

    为了让这个模块可以同时在PHP 4和PHP 5使用,我们需要使用一些宏去声明是依赖于PHP 4或是PHP 5的。因为PHP 4和PHP 5的执行文件是不兼容的,所以你需要为这两个PHP的版本分别编译不同的版本。

    通常我会把这些宏的声明放在一个单独的文件中。在这个例子里,就放在objects.h 中吧。

    objects.h 中要写一些PHP 5需要的函数,如:

    #ifndef __PHP5CPP_OBJECTS_H__

    #define __PHP5CPP_OBJECTS_H__

     

    #if PHP_MAJOR_VERSION == 5

     

    zend_class_entry *php5cpp_ce_myclass;

    static zend_object_handlers php5cpp_object_handlers_myclass;

     

    function_entry php5cpp_myclass_methods[] = {

        ZEND_ME_MAPPING(MyClass,    myclass_new,        NULL)

        ZEND_ME_MAPPING(setString,  myclass_set_string, NULL)

        ZEND_ME_MAPPING(getString,  myclass_get_string, NULL)

        {NULL, NULL, NULL}

    };

     

    typedef enum {

        is_myclass

    } php5cpp_obj_type;

     

    struct php5cpp_obj {

        zend_object std;

        int rsrc_id;

        php5cpp_obj_type type;

        union {

            MyClass *myclass;

        } u;

    };

    你可以发现,为了保持一致,每个声明都加上了php5cpp_ 前缀。这是习惯上的一种约定。

    另外,#if PHP_MAJOR_VERSION == 5表明在下边的那些宏、函数等都只是在PHP 5下才生效,当我们在PHP 4下编译时它们会被预处理忽略掉。

    php5cpp_ce_myclass 是类 MyClass 的入口。 php5cpp_object_handlers_myclass 是类的内部处理handler(句柄)。

    php5cpp_myclass_methods[] 把MyClass中的函数影射成可在PHP使用的标准函数。这样我们在PHP中就可以使用 myclass_new, myclass_get_string 等来执行这些函数。你会发现这里并没有定义myclass_destroy函数,因为在你对一个类实例使用unset() 时,系统会自动调用它的释构函数的了。

    在结构php5cpp_obj中的枚举变量 php5cpp_obj_type 声明了对象的类型。如果你想在扩展中再加入一个类,如: AnotherClass,你需要再增加一项,如: is_anotherclass

    php5cpp_obj 结构中声明了在PHP中使用的一些基本信息,包括:

    一个resource ID:rsrc_id ,它指向PHP内部的一个记录着C++对象的track的resource(资源)。实际上,我们的类在PHP中的操作是更像是一个PHP resource,它有自己的垃圾回收和引用计数等机制。

    一个zend_object 来声明我们的PHP类,使得它可以像PHP的类那样。当使用这个PHP类时,实际上就会调用我们的C++类去处理了。(需要记住的是:使用时需要像使用其它PHP类那样动态的创建它。(译:使用new))

    type 声明现在处理的对象。在这里,它只有一个:is_myclass。但正如我前边说过,你可以再增加其它的类。

    联合类型变量u 保存了指向C++对象实例的指针。如果你在这个扩展中还有其它class,那还需要在这再增加其它的指针,如:AnotherClass *anotherclass

    static void php5cpp_object_free_storage(zend_object *object TSRMLS_DC)

    {

        php5cpp_obj *obj = (php5cpp_obj *) object;

     

        zend_hash_destroy(obj->std.properties);

        FREE_HASHTABLE(obj->std.properties);

     

        if (obj->u.myclass) {

            zend_list_delete(obj->rsrc_id);

        }

     

        efree(obj);

    }

     

    static void php5cpp_object_new(zend_class_entry *class_type, zend_object_handlers *handlers, zend_object_value *retval TSRMLS_DC)

    {

        zval *tmp;

        php5cpp_obj *obj = (php5cpp_obj *) emalloc(sizeof(php5cpp_obj));

        memset(obj, 0, sizeof(php5cpp_obj));

        obj->std.ce = class_type;

     

        ALLOC_HASHTABLE(obj->std.properties);

        zend_hash_init(obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);

        zend_hash_copy(obj->std.properties, &class_type->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));

        retval->handle = zend_objects_store_put(obj, NULL, php5cpp_object_free_storage, NULL TSRMLS_CC);

        retval->handlers = handlers;

    }

    php5cpp_object_free_storage() 可以看成了对象是 php5cpp_obj 的释构函数,因为它要做的就是把MyClass的对象释放掉

    php5cpp_object_new() 基本上算是一个构造函数,它负责分配内存空间,分配zend_object结构,及初始化handler(句柄)等等。它负责为扩展中所有的类的构造,不管是对象MyClass 还是 AnotherClass

    php5cpp_object_new_myclass() 通过调用 php5cpp_object_new() 来创建一个PHP MyClsss的实例。如果在扩展中有几个类的话,你要为每个类写一个类似php5cpp_object_new_*()函数。

    // Register the class entry..

     

    #define PHP5CPP_REGISTER_CLASS(name, obj_name) /

        { /

            zend_class_entry ce; /

            INIT_CLASS_ENTRY(ce, obj_name, php5cpp_ ## name ##_methods); /

            ce.create_object = php5cpp_object_new_ ## name; /

            php5cpp_ce_ ## name = zend_register_internal_class_ex(&ce, NULL, NULL TSRMLS_CC);  /

            memcpy(&php5cpp_object_handlers_ ## name, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); /

            php5cpp_object_handlers_ ## name.clone_obj = NULL; /

            php5cpp_ce_ ## name->ce_flags |= ZEND_ACC_FINAL_CLASS; /

        }

     

     

    // Register resources. If we're using an object, put it into the object store.

     

    #define PHP5CPP_REGISTER_RESOURCE(obj_type, return_value, res, le) /

        { /

            int rsrc_id = ZEND_REGISTER_RESOURCE(object ? NULL : return_value, res, le); /

            if (object) { /

                php5cpp_obj *obj = (php5cpp_obj *) zend_object_store_get_object(object TSRMLS_CC); /

                obj->u.obj_type= res; /

                obj->rsrc_id = rsrc_id; /

                obj->type = is_ ## obj_type; /

            } /

        }

     

    #define PHP5CPP_REGISTER_MYCLASS_RESOURCE(return_value, res, le) /

        PHP5CPP_REGISTER_RESOURCE(myclass, return_value, res, le)

    接着我们要在php5cpp.cpp中加上以上的宏。虽然看起来很相似,但不要把它们弄乱,实际上PHP5CPP_REGISTER_CLASS()PHP5CPP_REGISTER_RESOURCE()的处理是不一样的。

    PHP5CPP_REGISTER_CLASS() 登记一个类的实际处理的程序。在后边介绍模块初始化函数(PHP_MINIT_FUNCTION())时还会遇到它。

    PHP5CPP_REGISTER_RESOURCE() 负责在登记一个resource时取得resource得ID。其实resource就是我们的C++对象的一个实例。当我们处理一个对象时,它会创建一个PHP对象,把实际的对象保存在PHP的对象贮存器中,然后把resource ID记录在PHP对象中。

    PHP5CPP_REGISTER_MYCLASS_RESOURCE() 只是为了使用 PHP5CPP_REGISTER_RESOURCE()时可以方便一些。

    // These are for parsing parameters and getting the actual object from the store.

     

    #define PHP5CPP_GET_THIS() /

        zval* object = getThis();

     

    #define PHP5CPP_SET_OBJ(type) /

        php5cpp_obj *obj = (php5cpp_obj *) zend_object_store_get_object(object TSRMLS_CC); /

        type ## _instance = obj->u.type;

     

    #define PHP5CPP_OBJ_PARAMS(type, params) /

        PHP5CPP_GET_THIS(); /

        if (object) { /

            if (params == FAILURE) { /

                RETURN_FALSE; /

            } /

            PHP5CPP_SET_OBJ(type) /

        } /

        else

     

    #define PHP5CPP_OBJ_NO_PARAMS(type) /

        PHP5CPP_GET_THIS(); /

        if (object) { /

            if (ZEND_NUM_ARGS() != 0) { /

                php_error(E_WARNING, "didn't expect any arguments in %s()", get_active_function_name(TSRMLS_C)); /

            } /

            PHP5CPP_SET_OBJ(type) /

        } /

        else

     

    #define PHP5CPP_MYCLASS_OBJ_PARAMS(params)  PHP5CPP_OBJ_PARAMS(myclass, params)

    #define PHP5CPP_MYCLASS_OBJ_NO_PARAMS()     PHP5CPP_OBJ_NO_PARAMS(myclass)

     

    PHP5CPP_GET_THIS() 会返回当前使用对象的指针。如果当前使用的是一个结构,那么getThis() 会返回NULL;如果使用的是一个对象,getThis() 返回一个指向当前PHP对象的指针。

    PHP5CPP_SET_OBJ() 会从对象贮存器中取得PHP对象实际使用的C++对象实例,然后可以用来做其它处理。在使用PHP函数/方法时,对象会贮存在类似"type ## _instance" 的类型里,如:在我们的例子中是myclass_instance,即MyClass*类型。

    PHP5CPP_*_OBJ_PARAMS()PHP5CPP_*_NO_OBJ_PARAMS() 会在调用我们的函数/方法时被使用,它们会处理从PHP方传进来的参数。在封装函数和声明中,可以通过zend_parse_parameters() 去分析这些参数。

    你可以注意到,宏PHP5CPP_*_PARAMS() 是以 else结尾的。这样的话,当所处理的不是一个对象时,它会试着用结构化方式去处理。这些宏都可以在下边找到。

    PHP 5写的类处理的部分已经写完了,下边的部分是为PHP 4写的很简单易懂的处理。如果我们用的是PHP 4,上边的那部分代码在预处理时会被乎略掉,而只处理下边的这些代码。如果我们用的是PHP 5则反之。

    #else // End of PHP5-specific macros

     

     

    // This stuff is for PHP 4. They're empty on purpose, obviously.

     

    #define PHP5CPP_GET_THIS()

    #define PHP5CPP_MYCLASS_OBJ_PARAMS(params)

    #define PHP5CPP_MYCLASS_OBJ_NO_PARAMS()

    #define PHP5CPP_REGISTER_CLASS(name, obj_name)

     

    #define PHP5CPP_REGISTER_MYCLASS_RESOURCE(return_value, res, le) /

        ZEND_REGISTER_RESOURCE(return_value, res, le);

     

    #endif // End of PHP4-specific macros

    非常的简单,除了PHP5CPP_REGISTER_MYCLASS_RESOURCE()外其它的宏都是空的。PHP5CPP_REGISTER_MYCLASS_RESOURCE()仍会注册一个resource,但不会为对象做任何的检查。好了,完成这些后,就使得代码在PHP 4和PHP 5中都可以编译通过了。

    下边的一些宏在PHP 4和PHP 5中都是一样的,它们是处理结构和非面向对象代码的。

    // These are for both PHP 4 and 5

     

    #define PHP5CPP_MYCLASS_RSRC_PARAMS(params) /

        if (params == FAILURE) { /

            RETURN_FALSE; /

        } /

        else { /

            ZEND_FETCH_RESOURCE(myclass_instance, MyClass*, &resource, -1, "myclass", le_myclass); /

        }

     

    #define PHP5CPP_MYCLASS_RSRC_NO_PARAMS() /

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) { /

            RETURN_FALSE; /

        } /

        else { /

            ZEND_FETCH_RESOURCE(myclass_instance, MyClass*, &resource, -1, "myclass", le_myclass); /

        }

     

    static ZEND_RSRC_DTOR_FUNC(destroy_myclass)

    {

        if (rsrc->ptr) {

            delete (MyClass*) rsrc->ptr;

            rsrc->ptr = NULL;

        }

    }

     

    #endif

    它们同样是很简单的:先试着分析PHP界面传进来的参数,和取得要处理的resource。

    destroy_myclass 负责资源回收。当触发垃圾回收机制,或destroy 对象/ resource 时,它会把在创建C++对象实例时分配的内存清理/释放掉。

    php5cpp.cpp里我们会这样使用这些宏:

    PHP5CPP_MYCLASS_OBJ_PARAMS(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s, &len))

    PHP5CPP_MYCLASS_RSRC_PARAMS(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &resource, &s, &len))

    PHP 5中,经过预处理后会变成这样:

    zval* object = getThis();

    if (object) {

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s, &len) == FAILURE) {

            RETURN_FALSE;

        }

        php5cpp_obj *obj = (php5cpp_obj *) zend_object_store_get_object(object TSRMLS_CC);

        myclass_instance = obj->u.myclass;

    }

    else if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &resource, &s, &len) == FAILURE) {

        RETURN_FALSE;

    }

    else {

        ZEND_FETCH_RESOURCE(myclass_instance, MyClass*, &resource, -1, "myclass", le_myclass); /

    }

    可以看到,在面向对象模式下,它会试着去取得对象,分析参数和取得实际的MyClass对象。如果在非面向对象的模式下,这段代码会试着用结构的方式去分析参数,取得resource等等。

    PHP 4中,经过预处理后会变成了下边这样:

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &resource, &s, &len) == FAILURE) {

        RETURN_FALSE;

    }

    else {

        ZEND_FETCH_RESOURCE(myclass_instance, MyClass*, &resource, -1, "myclass", le_myclass); /

    }

    在这里(PHP 4中),面向对象的代码会完全给忽略掉,只留下结构化程序的代码。

    为了上边我们做的有意义,现在我们要做一个最简单的部分,写我们的封装代码,和使得在PHP中可以使用我们的class。

    3  封装代码

    我们要在php_php5cpp.h中加入一些封装函数和方法的定义:

    PHP_FUNCTION(myclass_new);

    PHP_FUNCTION(myclass_destroy);

    PHP_FUNCTION(myclass_set_string);

    PHP_FUNCTION(myclass_get_string);

    简单吧。好,那我们在php5cpp.cpp中写入封装函数的执行代码:

    PHP_FUNCTION(myclass_new)

    {

        MyClass *myclass_instance;

        char* s;

        int len = 0;

        PHP5CPP_GET_THIS();

     

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &s, &len) == FAILURE) {

            RETURN_FALSE;

        }

     

        if (len) {

            myclass_instance = new MyClass(string(s, len));

        }

        else {

            myclass_instance = new MyClass;

        }

     

        if (myclass_instance != NULL) {

            PHP5CPP_REGISTER_MYCLASS_RESOURCE(return_value, myclass_instance, le_myclass);

        }

    }

     

    PHP_FUNCTION(myclass_destroy)

    {

        zval *resource;

     

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) {

            RETURN_FALSE;

        }

     

        zend_list_delete(Z_RESVAL_P(resource));

    }

     

    PHP_FUNCTION(myclass_set_string)

    {

        zval *resource;

        MyClass *myclass_instance;

        int len = -1;

        char *s;

        PHP5CPP_MYCLASS_OBJ_PARAMS(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s, &len))

        PHP5CPP_MYCLASS_RSRC_PARAMS(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &resource, &s, &len))

     

        if (myclass_instance == NULL) {

            php_error(E_WARNING, "can't set string on null resource in %s()", get_active_function_name(TSRMLS_C));

            RETURN_FALSE;

        }

        else {

            myclass_instance->setString(string(s, len));

            RETURN_TRUE;

        }

    }

     

    PHP_FUNCTION(myclass_get_string)

    {

        zval *resource;

        MyClass *myclass_instance = NULL;

        string retval;

        PHP5CPP_MYCLASS_OBJ_NO_PARAMS()

        PHP5CPP_MYCLASS_RSRC_NO_PARAMS()

     

        if (myclass_instance == NULL) {

            php_error(E_WARNING, "can't get string from null resource in %s()", get_active_function_name(TSRMLS_C));

            RETURN_FALSE;

        }

        else {

            retval = myclass_instance->getString();

            RETVAL_STRINGL((char*) retval.data(), retval.length(), 1);

        }

    }

    这里没什么太难的地方,所以我不想每个函数都解释一次。现在就只简单看一下myclass_new()myclass_set_string()做为例子。

    myclass_new()中,首先尝试取得一个对象。再次说明,只在面向对象方式下才会生效,在PHP 4下会被预处理所忽略掉。

    因为C++类的构造函数有默认的参数,所以我们要看看PHP是否有传一个string类型的参数过来。分析完传进来的参数后,就为分配myclass_instance内存空间,然后用PHP5CPP_REGISTER_MYCLASS_RESOURCE把得到的resource贮存起来。在PHP 5的OO模式下,PHP5CPP_REGISTER_MYCLASS_RESOURC会把resource当成对象贮存,在PHP 4的结构化模块下只会简单的创建一个resource。

    接着说myclass_set_string()...

    首先,先定义一个zval结构去处理在PHP用结构化接口传进来的参数。myclass_instance会被用来指向实际的C++对象。len*s用来保存从PHP方传来的字串和字串的长度。

    PHP 4下,PHP5CPP_MYCLASS_OBJ_PARAMS()会被预处理忽略。在PHP 5它会尝试取得对象,如果获取对象失败,否会返回使用结构化接口的步骤。

    在取得myclass_instance后,可以像平时使用类那样使用C++ MyClass类。先调用MyClass的setString()方法。然后把得到的C++标准string类对象转成类似C方式的char指针,并传回给PHP方。

    最后,要让PHP知道这个新class,很简单,在PHP_MINIT_FUNCTION中加入:

    PHP5CPP_REGISTER_CLASS(myclass, "MyClass");

     

    le_myclass = zend_register_list_destructors_ex(destroy_myclass, NULL, "myclass", module_number);

    好了,到这里代码已经写完了。:P

     7节.然后...

    然后,你可以按自己需要的方式编译扩展模块。你也许需要增加一些另外的代码,像using namespace std 等。这个介绍的下边几页会包含一个完整的可运行例子。这些代码我已经在gentoo系统上测试过了,包括目前(2004-03-19)在CVS上是新的PHP 5和PHP_4_3的环境。(译:本译文只提供了可查看代码的链接,没有包括这些代码)

     8节.例子源代码

    译文中没有包括完整在例子源代码,你通过下边的链接去查看:

    http://bugs.tutorbuddy.com/php5cpp/php5cpp/example_code.html

     

     9节.授权许可

    本文在GNU Free Documentation License下授权传播。以下是许可License的内容链接...

    http://bugs.tutorbuddy.com/php5cpp/php5cpp/license.html

     

     10节.修改历史

    10 Jun 2004  fishchen translated.

    6  May 2004  version 0.1.0.

    29 Mar 2004  First version.

     


    最新回复(0)