php $c=explore,PHP7扩展开发教程[1] – 怎样导出一个模块?

受PHP-X项目启发,我决定在未来的一段时间编写一系列php7扩展开发教程,一方面是沉淀最近一段时间的php7扩展开发知识,另外也可以将学习成果贡献给更多需要参与到php7扩展开发中的有志之士们。

在编写该系列博客时,我力求每个章节的功能点高度聚焦,代码保持短小,将需要了解的基础知识点介绍给大家,同时也为大家自行探索更多丰富的php api提供必要的线索,”授人以渔”是该系列博客的初衷。

每一个章节的代码将维护在github项目:php7-extension-explore,后续会随着教程的逐步编写不断丰富,最后会考虑将博客的内容整理到gitbook,但暂时仍旧在wordpress中撰写与提供查阅。

本博客假设你熟悉c和php开发,因此将不对语言细节、搭建方法做深入的讲解。

尽量避免使用Zend的宏定义,展示原生API和数据结构,方便大家深入理解。

只运行在linux+nginx+php-fpm环境下,线程安全之类的问题完全不涉及。

正式开始

本章节会搭建第一个php7扩展叫做myext,最终的目标是编译出一个扩展myext.so,并令php加载扩展,同时可以在phpinfo()中看到扩展的一些说明信息。

myext.h

头文件myext.h中引入了php.h,它包含了我们常用的php扩展API定义。另外一个头文件是ext/standard/info.h,ext是php的扩展目录,standard是扩展的名字,我们的扩展会用到这个扩展里的方法。

TRACE宏定义是为了调试扩展临时定义的日志函数,它会打印代码位置和信息到屏幕,便于我们调试追踪。

myext.c

扩展编译产生so,php/php-fpm程序会通过dlopen加载我们的myext.so扩展,然后通过dlsym找到get_module这个符号并调用它得到我们的扩展定义信息。

ZEND_DLEXPORT zend_module_entry *get_module() {    return &module;}

zend_module_entry定义了扩展的各种信息,可以在php源码的Zend/zend_modules.h中找到:

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_t globals_size;#ifdef ZTS        ts_rsrc_id* globals_id_ptr;#else        void* globals_ptr;#endif        void (*globals_ctor)(void *global);        void (*globals_dtor)(void *global);        int (*post_deactivate_func)(void);        int module_started;        unsigned char type;        void *handle;        int module_number;        const char *build_id;};

字段好多,但是填充它的代码要短的多:

zend_module_entry module = {    STANDARD_MODULE_HEADER_EX,  // size,zend_api,zend_debug,zts    NULL,   // ini_entry    NULL,   // deps    "myext",    //name    NULL,   // functions    extension_startup,  // module_startup_func    extension_shutdown, // module_shutdown_func    extension_before_request,   // request_startup_func    extension_after_request,    // request_shutdown_func    extension_info, // info_func    "1.0",  // version    // globals_size,globals_ptr,globals_ctor,globals_dtor,post_deactivate_func,module_started,type,    // handle,module_number,build_id    STANDARD_MODULE_PROPERTIES,};

这个宏填充了结构体前面那些并不重要的字段,直到ini_entry字段为之:

STANDARD_MODULE_HEADER_EX

这个宏填充了结构体后面那些并不重要的字段:

STANDARD_MODULE_PROPERTIES

至于这2个宏填充了哪些字段都已经注释说明,目前还没有具体使用过这些字段。

在目前使用到的字段中,name字段是模块的名称,version是模块的版本,其他字段后面的章节再慢慢涉及。

本章要重点关注的是extension_系列函数,它们涉及到扩展的生命期概念。

module_startup_function和module_shutdown_function分别在扩展加载和销毁时回调,对CGI/FPM都是一样的,都是进程启动调用一次,进程退出调用一次。

request_startup_function和request_shutdown_function分别在请求处理前和处理后回调,对于CGI来说就是启动和退出的2个时机,对于FPM来说是一个fastcgi请求处理前和处理后2个时机。

int extension_startup(int type, int module_number) {    TRACE("extension_startup");    return SUCCESS;}

以extension_startup函数为例,它的参数是type和module_number,其他3个函数也是一样的。

module_number是运行时分配的一个扩展唯一ID,后面章节会用得到。type表示扩展是持久的还是临时的,我们通过php.ini配置加载的扩展都是持久的,而在PHP代码里通过dl函数加载的叫做临时的,所以type对我们一般没有用处。

再看一下无关痛痒的extensin_info函数:

void extension_info(zend_module_entry *zend_module) {    php_info_print_table_start();    php_info_print_table_header(2, "myext support", "enabled");    php_info_print_table_row(2, "author", "owenliang");    php_info_print_table_row(2, "course name", "course1-how-to-export-a-module");    php_info_print_table_end();}

这个函数可以在php的phpinfo()中输出扩展的一些html描述信息,php_info_print_….这些api来自于ext/standard扩展,而这个扩展是php默认会安装的,你就不要担心这些函数符号找不到了。

这些函数的实现你可以在ext/standard/info.c中找到,这些函数符号存在于php的二进制中,myext扩展在被php加载时可以自动在php中找到符号并完成调用,你大可放心。

makefile

理解makefile非常重要,我们利用php-config程序获得php的头文件和库文件的所在位置,在编译扩展myext.so的时候包含进来即可:

PHP_INCLUDE = `php-config --includes`PHP_LIBS = `php-config --libs`PHP_LDFLAGS = `php-config --ldflags`PHP_INCLUDE_DIR = `php-config --include-dir`PHP_EXTENSION_DIR = `php-config --extension-dir`

你可以运行php-config系列命令,看看这些变量具体的内容。

最终我们将myext.c编译成myext.so,通过make && make install便会自动安装到PHP_EXTENSION_DIR目录下了。

为了加载so,我们根据php –ini找到php.ini的所在位置,修改它将myext.so扩展配置进去:

extension=/path/to/your/myext.so

然后执行php -m | grep myext确认扩展已成功加载:

$ php -m|grep myextextension_startup(myext.c:4) - extension_startupextension_before_request(myext.c:14) - extension_before_requestextension_after_request(myext.c:19) - extension_after_requestmyextextension_shutdown(myext.c:9) - extension_shutdown

正常情况下会看到这些信息,因为php -m 属于CLI执行,因此它会经历完整的php生命期,程序里打印的TRACE日志都得以输出。

你也可以通过make test来重复测试,它相当于执行php test.php。

结语

通过本章,掌握:

导出扩展

编译扩展

加载扩展

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值