`

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

阅读更多
一、前言
在我的上一篇文章“实战:用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
分享到:
评论

相关推荐

    【卷一/共两卷】AJAX实战pdf高清版90M

    第二部分 核心技术 第4章 作为应用的页面 4.1一种不同类型的MVC 4.1.1 以不同的规模重复MVC模式 4.1.2 在浏览器端应用MVC 4.2 Ajax应用中的视图 4.2.1 将逻辑从视图中分离 4.2.2 保持视图与逻辑的分离 4.3 Aiax应用...

    用纯C语言实现的一些项目,C语言入门级的实战程序 学生信息管理系统、俄罗斯方块游戏.zip

    对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同...

    ThinkPHP实战(夏磊)

    2.2.1C函数 16 2.2.2读取配置 17 2.2.3加载扩展配置 19 2.2.4写入配置 20 2.3 小结 23 第3章 路 由 24 3.1URL的三种模式 24 3.1.1动态URL 24 3.1.2静态URL 25 3.1.3伪静态URL 25 3.2ThinkPHP的路由 25 ...

    RabbitMQ实战 高效部署分布式消息队列完整版带书签

    RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展...

    卡尔曼实战——C和C++语言实现(源代码).zip

    对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步...

    卡尔曼实战-C和C++语言实现(源代码).zip

    对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步...

    基于卡尔曼实战——C和C++语言实现(源代码).zip

    对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步...

    EfsFrame(php开发框架) 2.2.rar

    Efs框架从研发到时间,历时近10年,积累了大量实战软件工程专家、数学专家的心血不断完善而成,已应用的大大小小的项目几十个,从小项目的开发管理维护设计到大项目的负载均衡设计,Efs逐渐形成了一整套完整的基于B/...

    EfsFrame(php开发框架) v2.2 源代码.rar

    Efs框架从研发到时间,历时近10年,积累了大量实战软件工程专家、数学专家的心血不断完善而成,已应用的大大小小的项目几十个,从小项目的开发管理维护设计到大项目的负载均衡设计,Efs逐渐形成了一整套完整的基于B/...

    RabbitMQ实战 高效部署分布式消息队列

    RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展...

    PostgreSQL从入门到精通

    无论你是使用PHP或者Perl开发动态网页、用Java或者C#开发企业级应用程序或者用C写一个客户端程序,你将找到你想要的章节。 这是PostgreSQL从入门到精通的第二版;第一版在2001年发布。从那时起,每章的内容都根据8.0...

    A Guide to Porting C C++ to Rust 等53本

    A Guide to Porting C C++ to Rust.epub Build a Node.js Project from Scratch.epub Build your applications with Webpack.epub Build Your Own Lisp 中文版.epub C 语言进阶.epub Ceph 运维手册.epub Chromium...

    EfsFrame(java开发框架) v2.2 源代码.rar

    Efs框架从研发到时间,历时近10年,积累了大量实战软件工程专家、数学专家的心血不断完善而成,已应用的大大小小的项目几十个,从小项目的开发管理维护设计到大项目的负载均衡设计,Efs逐渐形成了一整套完整的基于B/...

    EfsFrame(net开发框架) v2.2 源代码.rar

    Efs框架从研发到时间,历时近10年,积累了大量实战软件工程专家、数学专家的心血不断完善而成,已应用的大大小小的项目几十个,从小项目的开发管理维护设计到大项目的负载均衡设计,Efs逐渐形成了一整套完整的基于B/...

    java学生宿舍管理系统源码-zcnote:笔记类,包括mysql,php,nginx,linux,go,python,算法等等

    php扩展 swoole thinkphp yii2 python scrapy redis 应用实例 security 其他 分布式 安全 容器技术 docker 微服务学习笔记系列 Go微服务实战系列 搜索引擎elasticsearch 操作系统 数据结构和算法 leetcode 树 架构...

    RabbitMQ消息中间件实战(附讲义和源码)

    RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展...

    php curl 模拟登录并获取数据实例详解

    效率就要稍低些,当然,使用curl时需要开启下curl扩展。 代码实战 先来看登录部分的代码: //模拟登录 function login_post($url, $cookie, $post) { $curl = curl_init();//初始化curl模块 curl_setopt($curl, ...

Global site tag (gtag.js) - Google Analytics