欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实战:用C写php扩展(二)

程序员文章站 2022-04-17 20:57:06
...
一、前言
在我的上一篇文章“实战:用C写php扩展(一)”里介绍了一个最简单的php扩展myExt的创建过程。下面我们来研究一下这个扩展的源码的主要结构。
首先来了解一下PHP的三种建立功能模块的方法:建立一个外部模块;建立一个内建模块;Zend Engine扩充。
外部模块能在脚本运行时使用函数dl( )进行装载。dl函数从磁盘上装载一个模块,当脚本进行到这个模块部分时,就可获得相应的功能。当这个脚本程序结束,外部模块就从内存中清除。
内建模块是直接编译进PHP中的,并且作用于每个PHP进程;每个运行的脚本都可以直接调用她们的功能。当开发的是可以保持相对独立的、有固定功能的模块,我们需要她有更好的执行性能,或者这个模块在站点上被许多脚本频繁使用,那么内建模块是最好的选择。重新编译的代价很快会被PHP获得更快、更易使用的好处所补偿。然而,如果需要的只是一个功能不大的模块,内建模块不是理想的选择。
如果需要改变语言的解释程序或者需要在内核中直接集成特殊的功能,修改Zend Engine是首选。但是,一般来说,应该避免修改Zend 引擎。改变Zend 引擎会导致同现行的PHP不兼容,任何人都很难适应对Zend引擎改变所带来的变化。这种修改不能同PHP主力开发队伍分开,而且,随着PHP下一次“官方”升级,这种修改会被忽略掉。因此,这一扩充方法很少实际使用。
二、实战
一个php扩展的源码主要由以下几部分组成
1)头文件(包含了所有必须的宏定义,API定义等。)
2)C格式的模块函数外部声明
3)Zend 函数区声明
4)Zend 模块区声明
5)使用get_module()
6)模块函数功能的具体实现

我们先来看一下php_myExt.h头文件,上述1),2)
[c-sharp] view plaincopyprint?
/* 1)必须包含的头文件 */  
#include "php.h"  
...... 
 
PHP_MINIT_FUNCTION(myExt); 
PHP_MSHUTDOWN_FUNCTION(myExt); 
PHP_RINIT_FUNCTION(myExt); 
PHP_RSHUTDOWN_FUNCTION(myExt); 
PHP_MINFO_FUNCTION(myExt); 
 
/* 2)C格式的模块函数外部声明 */ 
PHP_FUNCTION(confirm_myExt_compiled);   /* 声明导出函数 confirm_myExt_compiled,该函数可以在php代码中使用 */ 
 
...... 
 
Zend 提供了一个宏PHP_FUNCTION(my_function),用来声明需要导出的导出函数(也就是让其成为可以被 PHP 脚本直接调用的原生函数)。 
confirm_myExt_compiled函数是自动生成的导出函数,主要是为了测试我们写的php扩展是否好使。当我们写完自己的php扩展后,可以删除该函数。 
我们可以增加如下一行来声明我们自己的导出函数myfunc: 
PHP_FUNCTION(myfunc); 


接下来,我们来看看myExt.c源文件,上述3),4),5),6)
[c-sharp] view plaincopyprint?
/** 
* 3)Zend 函数区声明,让Zend引擎知道此模块中有什么函数
* Every user visible function must have an entry in myExt_functions[].
* 需要将我们定义的需要导出的函数在此声明,从而将其引入 Zend。可以调用zend提供的PHP_FE(name, arg_types)宏来声明,arg_types参数一般被设为NULL
*/ 
const zend_function_entry myExt_functions[] = { 
    PHP_FE(confirm_myExt_compiled,  NULL)       /* For testing, remove later. 正式发布我们的代码时,需要删除 */ 
    {NULL, NULL, NULL}  /* Must be the last line in myExt_functions[] */ 
}; 
 
我们需要在myExt_functions[]中添加我们的myfunc函数的引入声明 
const zend_function_entry myExt_functions[] = { 
    PHP_FE(confirm_myExt_compiled,  NULL)       /* For testing, remove later. 正式发布我们的代码时,需要删除 */ 
    PHP_FE(myfunc, NULL) 
    {NULL, NULL, NULL}  /* Must be the last line in myExt_functions[] */ 
}; 
这个结构的最后一项是 {NULL, NULL, NULL} 。事实上,这个结构的最后一项也必须始终是 {NULL, NULL, NULL} ,因为 Zend Engine 需要靠它来确认这些导出函数的列表是否列举完毕。注意: 你不应该使用一个预定义的宏来代替列表的结尾部分(即{NULL, NULL, NULL}),因为编译器会尽量寻找一个名为 “NULL” 的函数的指针来代替 NULL ! 


接下来是Zend 模块的相关信息,保存在一个名为zend_module_entry 的结构中。
[c-sharp] view plaincopyprint?
/* 4)Zend 模块区声明  */ 
zend_module_entry myExt_module_entry = { 
#if ZEND_MODULE_API_NO >= 20010901 
    STANDARD_MODULE_HEADER, 
#endif 
    "myExt",                // 模块名称 
    myExt_functions,        // Zend 函数块的指针,就是上面我们讨论的数组myExt_functions[] 
    PHP_MINIT(myExt),       // 声明一个模块的启动函数 
    PHP_MSHUTDOWN(myExt),   // 声明一个模块的关闭函数 
    PHP_RINIT(myExt),       /* 声明一个请求的启动函数。Replace with NULL if there's nothing to do at request start */ 
    PHP_RSHUTDOWN(myExt),   /* 声明一个请求的关闭函数。Replace with NULL if there's nothing to do at request end */ 
    PHP_MINFO(myExt),       // 声明一个输出模块信息的函数,用于phpinfo()。 
#if ZEND_MODULE_API_NO >= 20010901 
    "0.1", /* Replace with version number for your extension */ 
#endif 
    STANDARD_MODULE_PROPERTIES 
}; 
 
可以在php源码 Zend/zend_modules.h中找到结构体zend_module_entry的定义。 
struct _zend_module_entry { 
    unsigned short size; 
    unsigned int zend_api; 
    unsigned char zend_debug; 
    unsigned char zts; 
    const struct _zend_ini_entry *ini_entry; 
    const struct _zend_module_dep *deps; 
    const char *name; 
    const struct _zend_function_entry *functions; 
    int (*module_startup_func)(INIT_FUNC_ARGS); 
    int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); 
    int (*request_startup_func)(INIT_FUNC_ARGS); 
    int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); 
    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); 
    const char *version; 
    ......  // 其余的一些我们不感兴趣的信息 
}; 

字段
说明
size, zend_api, zend_debug and zts
通常用 "STANDARD_MODULE_HEADER" 来填充,它指定了模块的四个成员:标识整个模块结构大小的 size ,值为 ZEND_MODULE_API_NO 常量的 zend_api,标识是否为调试版本(使用 ZEND_DEBUG 进行编译)的 zend_debug,还有一个用来标识是否启用了 ZTS (Zend 线程安全,使用 ZTS 或USING_ZTS 进行编译)的 zts。
name
模块名称 (像“File functions”、“Socket functions”、“Crypt”等等). 这个名字就是使用 phpinfo() 函数后在“Additional Modules”部分所显示的名称。
functions
Zend 函数块的指针, 这个我们在前面已经讨论过。
module_startup_func
模块启动函数。这个函数仅在模块初始化时被调用,通常用于一些与整个模块相关初始化的工作(比如申请初始化的内存等等)。如果想表明模块函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。可以通过宏 ZEND_MINIT 来声明一个模块启动函数。如果不想使用,请将其设定为 NULL。
module_shutdown_func
模块关闭函数。这个函数仅在模块卸载时被调用,通常用于一些与模块相关的反初始化的工作(比如释放已申请的内存等等)。这个函数和 module_startup_func() 相对应。如果想表明函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。可以通过宏ZEND_MSHUTDOWN 来声明一个模块关闭函数。如果不想使用,请将其设定为 NULL。
request_startup_func
请求启动函数。这个函数在每次有页面的请求时被调用,通常用于与该请求相关的的初始化工作。如果想表明函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。注意: 如果该模块是在一个页面请求中被动态加载的,那么这个模块的请求启动函数将晚于模块启动函数的调用(其实这两个初始化事件是同时发生的)。可以使用宏 ZEND_RINIT 来声明一个请求启动函数,若不想使用,请将其设定为 NULL。
request_shutdown_func
请求关闭函数。这个函数在每次页面请求处理完毕后被调用,正好与 request_startup_func() 相对应。如果想表明函数调用失败或请求初始化失败请返回 FAILURE,否则请返回 SUCCESS。注意: 当在页面请求作为动态模块加载时, 这个请求关闭函数先于模块关闭函数的调用(其实这两个反初始化事件是同时发生的)。可以使用宏 ZEND_RSHUTDOWN 来声明这个函数,若不想使用,请将其设定为 NULL 。
info_func
模块信息函数。当脚本调用 phpinfo() 函数时,Zend 便会遍历所有已加载的模块,并调用它们的这个函数。每个模块都有机会输出自己的信息。通常情况下这个函数被用来显示一些环境变量或静态信息。可以使用宏 ZEND_MINFO 来声明这个函数,若不想使用,请将其设定为 NULL 。
version
模块的版本号。如果你暂时还不想给某块设置一个版本号的话,你可以将其设定为 NO_VERSION_YET。但我们还是推荐您在此添加一个字符串作为其版本号。版本号通常是类似这样: "2.5-dev", "2.5RC1", "2.5" 或者 "2.5pl3" 等等。
Remaining structure elements
这些字段通常是在模块内部使用的,通常使用宏STANDARD_MODULE_PROPERTIES 来填充。而且你也不应该将他们设定别的值。STANDARD_MODULE_PROPERTIES_EX 通常只会在你使用了全局启动函数(ZEND_GINIT)和全局关闭函数(ZEND_GSHUTDOWN)时才用到,一般情况请直接使用 STANDARD_MODULE_PROPERTIES 。
[c-sharp] view plaincopyprint?
/* 5) get_module()函数 */ 
这个函数只用于动态可加载模块(*.so文件),而不能用于内建模块。通过宏ZEND_GET_MODULE 来创建 
#ifdef COMPILE_DL_MYEXT 
ZEND_GET_MODULE(myExt) 
#endif 
 
我们可以在php源码 Zend/zend_API.h中找到ZEND_GET_MODULE的宏定义 
#define ZEND_GET_MODULE(name) / 
    BEGIN_EXTERN_C()/ 
    ZEND_DLEXPORT zend_module_entry *get_module(void) { return &name##_module_entry; }/ 
    END_EXTERN_C() 
     
还记得我们在“实战:用C写php扩展(一)”里是调用phpize生成configure文件吗,生成的configure文件里定义了COMPILE_DL_MYEXT常量 
#define COMPILE_DL_MYEXT 1 
 
get_module() 函数在模块加载时被 Zend 所调用,你也可以认为是被你 PHP 脚本中的 dl() 函数所调用。这个函数的作用就是把模块的信息块传递 Zend 并通知 Zend 获取这个模块的相关内容。 
如果你没有在一个动态可加载模块中实现 get_module() 函数,那么当你在访问它的时候 Zend 就会向你抛出一个错误信息。 


[c-sharp] view plaincopyprint?
/* 6)模块函数功能的具体实现。*/  
导出函数的实现是我们构建扩展的最后一步。在我们的myExt例子中,函数被实现如下: 
 
PHP_FUNCTION(confirm_myExt_compiled) 

    char *arg = NULL; 
    int arg_len, len; 
    char *strg; 
 
    // 首先我们需要检查和接收这个函数的参数 
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { 
        return; 
    } 
 
    // 最后是函数的返回值 
    len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myExt", arg); 
    RETURN_STRINGL(strg, len, 0); 


http://blog.csdn.net/sunlylorn/article/details/6227160
相关标签: C php 扩展