├── 1.1.md ├── 1.2.md ├── 1.3.md ├── 1.4.md ├── 1.5.md ├── 1.md ├── 10.1.md ├── 10.2.md ├── 10.3.md ├── 10.4.md ├── 10.5.md ├── 10.6.md ├── 10.md ├── 11.1.md ├── 11.2.md ├── 11.3.md ├── 11.4.md ├── 11.md ├── 12.1.md ├── 12.2.md ├── 12.3.md ├── 12.4.md ├── 12.5.md ├── 12.6.md ├── 12.md ├── 13.1.md ├── 13.2.md ├── 13.md ├── 14.1.md ├── 14.2.md ├── 14.3.md ├── 14.4.md ├── 14.md ├── 15.1.md ├── 15.2.md ├── 15.3.md ├── 15.4.md ├── 15.5.md ├── 15.6.md ├── 15.md ├── 16.1.md ├── 16.2.md ├── 16.3.md ├── 16.md ├── 17.1.md ├── 17.2.md ├── 17.3.md ├── 17.4.md ├── 17.5.md ├── 17.md ├── 18.1.md ├── 18.2.md ├── 18.3.md ├── 18.md ├── 19.1.md ├── 19.2.md ├── 19.3.md ├── 19.4.md ├── 19.5.md ├── 19.md ├── 2.1.md ├── 2.2.md ├── 2.3.md ├── 2.4.md ├── 2.5.md ├── 2.6.md ├── 2.7.md ├── 2.md ├── 20.1.md ├── 20.2.md ├── 20.3.md ├── 20.4.md ├── 20.5.md ├── 20.6.md ├── 20.7.md ├── 20.md ├── 3.1.md ├── 3.2.md ├── 3.3.md ├── 3.md ├── 4.1.md ├── 4.2.md ├── 4.3.md ├── 4.4.md ├── 4.5.md ├── 4.md ├── 5.1.md ├── 5.2.md ├── 5.3.md ├── 5.4.md ├── 5.5.md ├── 5.md ├── 6.1.md ├── 6.2.md ├── 6.3.md ├── 6.md ├── 7.1.md ├── 7.2.md ├── 7.3.md ├── 7.4.md ├── 7.md ├── 8.1.md ├── 8.2.md ├── 8.3.md ├── 8.4.md ├── 8.md ├── 9.1.md ├── 9.2.md ├── 9.3.md ├── 9.4.md ├── 9.md ├── LICENSE ├── README.md ├── api.md ├── contributor.md ├── index.md ├── preface.md └── 约定... ... /1.1.md: -------------------------------------------------------------------------------- 1 | # 1.1 让我们从SAPI开始 2 | 3 | 我们平时接触的最多的是web模式下的php,当然你也肯定知道php还有个CLI模式(PHP5有多达22种SAPI,PHP7则精简去了一些不常用的SAPI目前支持8种)。 4 | 其实无论哪种模式,PHP的工作原理都是一样的, 5 | 都是作为一种SAPI在运行(Server Application Programming Interface: the API used by PHP to interface with Web Servers)。当我们在终端敲入php这个命令时候,它使用的是"command line sapi"!它就像一个mini的web服务器一样来支持php完成这个请求,请求完成后再重新把控制权交给终端。 6 | 7 | 简单来说, SAPI就是PHP和外部环境的代理器。它把外部环境抽象后, 为内部的PHP提供一套固定的, 统一的接口, 使得PHP自身实现能够不受错综复杂的外部环境影响,保持一定的独立性 8 | 9 | 更多内容参看来自Laruence的博客对SAPI的介绍: [深入理解Zend SAPIs](http://www.laruence.com/2008/08/12/180.html) 10 | 11 | ## links 12 | * [目录]() 13 | * 上一节: [PHP的生命周期](<1.md>) 14 | * 下一节: [PHP的启动与终止](<1.2.md>) 15 | 16 | -------------------------------------------------------------------------------- /1.2.md: -------------------------------------------------------------------------------- 1 | # 1.2 PHP的启动与终止 2 | 3 | PHP程序的启动可以看作有两个概念上的启动,终止也有两个概念上的终止。 4 | 其中一个是PHP作为Apache(拿它举例,板砖勿扔)的一个模块的启动与终止, 5 | 这次启动php会初始化一些必要数据,比如与宿主Apache有关的,**并且这些数据是常驻内存的!** 6 | 终止与之相对。 7 | 还有一个概念上的启动就是当Apache分配一个页面请求过来的时候,PHP会有一次启动与终止,这也是我们最常讨论的一种。 8 | 9 | 现在我们主要来看一个PHP扩展的生命旅程是怎样走完这四个过程的。 10 | 11 | 在最初的初始化时候,就是PHP随着Apache的启动而诞生在内存里的时候, 12 | 它会把自己所有已加载扩展的MINIT方法(全称Module Initialization,是由每个模块自己定义的函数。)都执行一遍。 13 | 在这个时间里,扩展可以定义一些自己的常量、类、资源等所有会被用户端的PHP脚本用到的东西。 14 | 但你要记住,这里定义的东东都会随着Apache常驻内存,可以被所有请求使用,直到Apache卸载掉PHP模块! 15 | 16 | 内核中预置了PHP_MINIT_FUNCTION宏函数,来帮助我们实现这个功能: 17 | 18 | ````c 19 | //抛弃作者那个例子,书才看两页整那样的例子太复杂了! 20 | //walu是我扩展的名称 21 | int time_of_minit;//在MINIT()中初始化,在每次页面请求中输出,看看是否变化 22 | PHP_MINIT_FUNCTION(walu) 23 | { 24 | time_of_minit=time(NULL);//我们在MINIT启动中对他初始化 25 | return SUCCESS;//返回SUCCESS代表正常,返回FALIURE就不会加载这个扩展了。 26 | } 27 | 28 | ```` 29 | 30 | 当一个页面请求到来时候,PHP会迅速的开辟一个新的环境,并重新扫描自己的各个扩展, 31 | 遍历执行它们各自的RINIT方法(俗称Request Initialization), 32 | 这时候一个扩展可能会初始化在本次请求中会使用到的变量等, 33 | 还会初始化等会儿用户端(即PHP脚本)中的变量之类的,内核预置了PHP_RINIT_FUNCTION()这个宏函数来帮我们实现这个功能: 34 | 35 | ````c 36 | int time_of_rinit;//在RINIT里初始化,看看每次页面请求的时候是否变化。 37 | PHP_RINIT_FUNCTION(walu) 38 | { 39 | time_of_rinit=time(NULL); 40 | return SUCCESS; 41 | } 42 | 43 | ```` 44 | 45 | 好了,现在这个页面请求执行的差不多了,可能是顺利的走到了自己文件的最后, 46 | 也可能是出师未捷,半道被用户给die或者exit了, 47 | 这时候PHP便会启动回收程序,收拾这个请求留下的烂摊子。 48 | 它这次会执行所有已加载扩展的RSHUTDOWN(俗称Request Shutdown)方法, 49 | 这时候扩展可以抓紧利用内核中的变量表之类的做一些事情, 50 | 因为一旦PHP把所有扩展的RSHUTDOWN方法执行完, 51 | 便会释放掉这次请求使用过的所有东西, 52 | 包括变量表的所有变量、所有在这次请求中申请的内存等等。 53 | 54 | 内核预置了PHP_RSHUTDOWN_FUNCTION宏函数来帮助我们实现这个功能 55 | 56 | ````c 57 | PHP_RSHUTDOWN_FUNCTION(walu) 58 | { 59 | FILE *fp=fopen("time_rshutdown.txt","a+"); 60 | fprintf(fp,"%ld\n",time(NULL));//让我们看看是不是每次请求结束都会在这个文件里追加数据 61 | fclose(fp); 62 | return SUCCESS; 63 | } 64 | 65 | ```` 66 | 前面该启动的也启动了,该结束的也结束了,现在该Apache老人家歇歇的时候,当Apache通知PHP自己要Stop的时候,PHP便进入MSHUTDOWN(俗称Module Shutdown)阶段。这时候PHP便会给所有扩展下最后通牒,如果哪个扩展还有未了的心愿,就放在自己MSHUTDOWN方法里,这可是最后的机会了,一旦PHP把扩展的MSHUTDOWN执行完,便会进入自毁程序,这里一定要把自己擅自申请的内存给释放掉,否则就杯具了。 67 | 68 | 内核中预置了PHP_MSHUTDOWN_FUNCTION宏函数来帮助我们实现这个功能: 69 | 70 | ````c 71 | PHP_MSHUTDOWN_FUNCTION(walu) 72 | { 73 | FILE *fp=fopen("time_mshutdown.txt","a+"); 74 | fprintf(fp,"%ld\n",time(NULL)); 75 | return SUCCESS; 76 | } 77 | 78 | ```` 79 | 这四个宏都是在walu.c里完成最终实现的,而他们的则是在/main/php.h里被定义的(其实也是调用的别的宏,本节最后我把这几个宏给展开了,供有需要的人查看)。 80 | 81 | **好了,现在我们本节内容说完了,下面我们把所有的代码合在一起,并预测一下应该出现的结果:** 82 | ````c 83 | //这些代码都在walu.c里面,不在.h里 84 | 85 | int time_of_minit;//在MINIT中初始化,在每次页面请求中输出,看看是否变化 86 | PHP_MINIT_FUNCTION(walu) 87 | { 88 | time_of_minit=time(NULL);//我们在MINIT启动中对他初始化 89 | return SUCCESS; 90 | } 91 | 92 | int time_of_rinit;//在RINIT里初始化,看看每次页面请求的时候是否变化。 93 | PHP_RINIT_FUNCTION(walu) 94 | { 95 | time_of_rinit=time(NULL); 96 | return SUCCESS; 97 | } 98 | 99 | PHP_RSHUTDOWN_FUNCTION(walu) 100 | { 101 | FILE *fp=fopen("/cnan/www/erzha/time_rshutdown.txt","a+");//请确保文件可写,否则apache会莫名崩溃 102 | fprintf(fp,"%d\n",time(NULL));//让我们看看是不是每次请求结束都会在这个文件里追加数据 103 | fclose(fp); 104 | return SUCCESS; 105 | } 106 | 107 | PHP_MSHUTDOWN_FUNCTION(walu) 108 | { 109 | FILE *fp=fopen("/cnan/www/erzha/time_mshutdown.txt","a+");//请确保文件可写,否则apache会莫名崩溃 110 | fprintf(fp,"%d\n",time(NULL)); 111 | fclose(fp); 112 | return SUCCESS; 113 | } 114 | 115 | //我们在页面里输出time_of_minit和time_of_rinit的值 116 | PHP_FUNCTION(walu_test) 117 | { 118 | php_printf("%d<br />",time_of_minit); 119 | php_printf("%d<br />",time_of_rinit); 120 | return; 121 | } 122 | 123 | 124 | ```` 125 | 126 | * time_of_minit的值每次请求都不变。 127 | * time_of_rinit的值每次请求都改变。 128 | * 每次页面请求结束都会往time_rshutdown.txt中写入数据。 129 | * 只有在apache结束后time_mshutdown.txt才写入有数据。 130 | 131 | > 多谢 [闸北陆小洪](http://weibo.com/showz) 指出的有关time_of_rinit的笔误。 132 | 133 | 上面便是PHP中典型的启动-终止模型,实际情况可能因为模式不同而有所变化, 134 | 到底PHP的启动-终止会有多少种不同变化方式,请看下一节。 135 | 136 | 137 | ## links 138 | * [目录]() 139 | * 上一节: [让我们从SAPI开始](<1.1.md>) 140 | * 下一节: [PHP的生命周期](<1.3.md>) 141 | 142 | -------------------------------------------------------------------------------- /1.3.md: -------------------------------------------------------------------------------- 1 | # 1.3 PHP的生命周期 2 | 3 | 一个PHP实例,无论通过http请求调用的,还是从命令行启动的,都会向我们上一节说的那样, 4 | 依次进行Module init、Request init、Request Shutdown、Module shutdown四个过程, 5 | 当然之间还会执行脚本自己的逻辑。 6 | 那么两种init和两种shutdown各会执行多少次、各自的执行频率有多少呢? 7 | 这取决与PHP是用什么sapi与宿主通信的。最常见的四种方式如下所列: 8 | 9 | * 直接以CLI/CGI模式调用 10 | * 多进程模式 11 | * 多线程模式 12 | * Embedded(嵌入式,在自己的C程序中调用Zend Engine) 13 | 14 | ## 1、CLI/CGI 15 | 16 | CLI和CGI的SAPI是相当特殊的,因为这时PHP的生命周期完全在一个单独的请求中完成。虽然简单,不过我们以前提过的两种init和两种shutdown仍然都会被执行。图1.1展示了PHP在这种模式下是怎么工作的。 17 | 18 |

19 | 20 | ## 2、多进程模式 21 | **[ps:书是2006年出版的,所以你应该理解作者说多进程是主流]** 22 | PHP最常见的工作方式便是编译成为Apache2 的Pre-fork MPM或者Apache1 的APXS 模式,其它web服务器也大多用相同的方式工作,在本书后面,把这种方式统一叫做多进程方式。 23 | 给它起这个名字是有原因的,不是随便拍拍屁股拍拍脑袋定下来的。 24 | 当Apache启动的时候,会立即把自己fork出好几个子进程,每一个进程都有自己独立的内存空间, 25 | 也就代表了有自己独立的变量、函数等。在每个进程里的PHP的工作方式如下图所示: 26 | 27 |

28 | 29 | 因为是fork出来的,所以各个进程间的数据是彼此独立,不会受到外界的干扰**(ps:fork后可以用管道等方式实现进程间通信)**。 30 | 这是一片独立天地,它允许每个子进程做任何事情,玩七十码、躲猫猫都没人管,办公室拿砍刀玩自杀也没事, 31 | 下图展示了从apache的视角来看多进程工作模式下的PHP: 32 | 33 |

34 | 35 | ## 3、多线程模式 36 | 随着时代的进步,PHP越来越多的在多线程模式下工作,就像IIS的isapi和Apache MPM worker**(支持混合的多线程多进程的多路处理模块)**。 37 | 在这种模式下,只有一个服务器进程在运行着,但会同时运行很多线程,这样可以减少一些资源开销, 38 | 像Module init和Module shutdown就只需要运行一次就行了,一些全局变量也只需要初始化一次, 39 | 因为线程独具的特质,使得各个请求之间方便的共享一些数据成为可能。 40 | 41 | > 其实多线程与MINIT、MSHUTDOWN只执行一次并没有什么联系,多进程模式下一样可以实现。 42 | 43 | 下图展示了在这种模式下PHP的工作流程: 44 |

45 | 46 | ## 4、Embed 47 | Embed SAPI是一种比较特殊的sapi,容许你在C/C++语言中调用PHP/ZE提供的函数。 48 | 并且这种sapi和上面的三种一样,按Module Init、Request Init、Rshutdown、mshutdown的流程执行着。 49 | 当然,这只是其中一种情况。因为特定的应用有自己特殊的需求,只是在处理PHP脚本这个环节基本一致。 50 | 51 | 真正令emebed模式独特的是因为它可能随时嵌入到某个程序里面去(**比如你的test.exe里**), 52 | 然后被当作脚本的一部分在一个请求的时候执行。 53 | 控制权在PHP和原程序间来回传递。关于嵌入式的PHP在第20章会有应用,到时我们再用实例介绍这个不经常使用的sapi。 54 | 55 | ## 关于Embed SAPI应用的文章 56 | * [Laruence大哥的使用PHP Embed SAPI实现Opcodes查看器](http://www.laruence.com/2008/09/23/539.html) 57 | 58 | 59 | ## links 60 | * [目录]() 61 | * 上一节 [PHP的启动与终止](<1.2.md>) 62 | * 下一节 [线程安全](<1.4.md>) 63 | 64 | -------------------------------------------------------------------------------- /1.4.md: -------------------------------------------------------------------------------- 1 | # 1.4 线程安全 2 | 3 | 4 | # Native TLS(Native Thread local storage,原生线程本地存储) 5 | PHP在多线程模式下(例如,Web服务器Apache的woker和event模式,就是多线程),需要解决“线程安全”(TS,Thread Safe)的问题,因为线程是共享进程的内存空间的,所以每个线程本身需要通过某种方式,构建私有的空间来保存自己的私有数据,避免和其他线程相互污染。而PHP5采用的方式,就是维护一个全局大数组,为每一个线程分配一份独立的存储空间,线程通过各自拥有的key值来访问这个全局数据组。 6 | 而这个独有的key值在PHP5中需要传递给每一个需要用到全局变量的函数,PHP7认为这种传递的方式并不友好,并且存在一些问题。因而,尝试采用一个全局的线程特定变量来保存这个key值。 7 | 8 | 不过这种方式(php5中使用的方式)不太好,她存在以下问题: 9 | 10 | * 1.许多函数不得不在TS模式下传递额外的参数(如TSRMLS_CC等),这使得我们在写代码时过多的使用TSRMLS_*宏。 11 | * 2.忘记传递TS参数在NTS(非线程安全模式下)不会带来任何问题,但在TS模式下会编译失败。 12 | * 3.另外,当我们在定义函数时如果在定义变量之前使用TSRMLS_FETCH()的话会造成C89的兼容性问题,这在Windows下进行扩展开发时更为明显。 13 | * 4.在TS模式时有些情况下需要进行条件编译(#ifdef ZTS)。 14 | * 5.当线程ID未明确的作为参数传递时,我们要使用TSRMLS_FETCH(),但这个宏的速度是很慢的。 15 | 16 | **传递线程的ID作为识别线程全局数据的手段这种方式在PHP7已经被移除了,换成了更合理的方式进行处理。** 理想情况下我们使用一个全局的线程特定变量来做。 17 | 18 | ## 向后兼容 19 | * 1.线程ID不会再被明确的作为参数进行传递,许多函数的(声明)定义不需要再使用TSRM_*,但 TSRM_*宏依然保留. 20 | * 2.一些使用#ifdef ZTS宏进行条件编译的地方需要进行修改。 21 | 22 | ## 实施细节 23 | PHP7是使用如下方式来实现的: 24 | 所有的(并非所有)TSRMLS_*宏被修改成了空的(比如TSRMLS_CC,TSRMLS_DC等)。 25 | 被用来访问全局数据的TSRMG宏可以通过以下两种方式进行替换: 26 | 27 | * 1.使用trsm_tls_cache()函数取得特定线程的全局变量,这个函数本身使用特定于操作系统相关的线程函数来取得全局变量(比如linux下的pthreads或者windows下的相关线程函数)。 28 | * 2.使用一个预定义的全局指针,然后每个线程更新这个指针来实现全局数据的访问与维护。 29 | * 3.php7使用上面的第二种方案 30 | 31 | 正如PHP的RFC中所写的那样,最主要困难是并不是每一个编译器(比如Visual Studio)都支持在共享对象间共享全局的线程特定变量。基于这个原因,函数必须至少调用tsrm_get_ls_cache()一次,以便使用线程相关的资源。这个操作需要在每个扩展的二进制版本中都要进行。 32 | 33 | 几乎每个全局访问宏(比如EG,CG等),都修改为使用特定于扩展的本地TSRMLS指针来访问全局变量。这需要三个步骤来做: 34 | 35 | * 1.为每一个扩展(如xx.so)定义一个指针变量来保存tsrmls的缓存即使用ZEND_TSRMLS_CACHE_DEFINE宏(展开后为TSRM_TLS void *TSRMLS_CACHE = NULL;)。 36 | 37 | ````c 38 | //extdev.c 扩展名为extdev 39 | #ifdef COMPILE_DL_EXTDEV 40 | #ifdef ZTS 41 | ZEND_TSRMLS_CACHE_DEFINE() 42 | #endif 43 | ZEND_GET_MODULE(extdev) 44 | #endif 45 | ```` 46 | 47 | * 2.在头文件中对外暴露上面字义的 void *TSRMLS_CACHE 指针使用ZEND_TSRMLS_CACHE_EXTERN宏(展开后即为extern TSRM_TLS void *TSRMLS_CACHE;)。 48 | 49 | ````c 50 | //php_extdev.h 51 | #if defined(ZTS) && defined(COMPILE_DL_EXTDEV) 52 | ZEND_TSRMLS_CACHE_EXTERN() 53 | #endif 54 | ```` 55 | 56 | * 3.提供一个为每个线程更新void *TSRMLS_CACHE的机制,这里使用ZEND_TSRMLS_CACHE_UPDATE宏(展开后即是TSRMLS_CACHE_UPDATE() if (!TSRMLS_CACHE) TSRMLS_CACHE = tsrm_get_ls_cache())。 57 | 58 | ````c 59 | //extdev.c 每次请求中更新void *TSRMLS_CACHE指针 60 | PHP_RINIT_FUNCTION(extdev) 61 | { 62 | #if defined(COMPILE_DL_EXTDEV) && defined(ZTS) 63 | ZEND_TSRMLS_CACHE_UPDATE(); 64 | #endif 65 | return SUCCESS; 66 | } 67 | ```` 68 | 69 | 70 | 头文件如下: 71 | ````c 72 | //zend.h 73 | #define ZEND_TSRMLS_CACHE_EXTERN() TSRMLS_CACHE_EXTERN() 74 | #define ZEND_TSRMLS_CACHE_DEFINE() TSRMLS_CACHE_DEFINE() 75 | #define ZEND_TSRMLS_CACHE_UPDATE() TSRMLS_CACHE_UPDATE() 76 | 77 | //TSRM.h 78 | #define TSRMLS_CACHE_EXTERN() extern TSRM_TLS void *TSRMLS_CACHE; 79 | #define TSRMLS_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE = NULL; 80 | #if ZEND_DEBUG 81 | #define TSRMLS_CACHE_UPDATE() TSRMLS_CACHE = tsrm_get_ls_cache() 82 | #else 83 | #define TSRMLS_CACHE_UPDATE() if (!TSRMLS_CACHE) TSRMLS_CACHE = tsrm_get_ls_cache() 84 | #endif 85 | ```` 86 | 87 | **在PHP7中我们使用ext_skel生成扩展时已经实现了这些。而且我们在以后的开发中也不需要再关心线程安全问题php7已经为我们做好了这一切。这在扩展开发上可以让我们节省不少代码,可以让我们更关注于业务的处理。** 88 | 89 | ## links 90 | * [目录]() 91 | * 上一节: [PHP的生命周期](<1.3.md>) 92 | * 下一节: [小结](<1.5.md>) 93 | 94 | -------------------------------------------------------------------------------- /1.5.md: -------------------------------------------------------------------------------- 1 | # 1.5 PHP的生命周期 2 | 3 | 这一章讲述了一些后续章节需要的基础概念,是你编写优质的PHP扩展的基础。 4 | 5 | ## links 6 | * [目录]() 7 | * 上一节: [线程安全](<1.4.md>) 8 | * 下一节: [PHP变量在内核中的实现](<2.md>) 9 | 10 | -------------------------------------------------------------------------------- /1.md: -------------------------------------------------------------------------------- 1 | # 1 PHP的生命周期 2 | 3 | ## 目录 4 | * 1. [让我们从SAPI开始](1.1.md) 5 | * 2. [PHP的启动与终止](1.2.md) 6 | * 3. [PHP的生命周期](1.3.md) 7 | * 4. [线程安全](1.4.md) 8 | * 5. [小结](1.5.md) 9 | 10 | 在平常的Web环境中,我们并不需要单独启动PHP,它一般都会作为一个模块自动加载到web-server里面去,如apache加载的libphp7.so。 11 | 只要我们启动了web-server,被一起加载的php便会和服务器一起解析被请求的php脚本。 12 | 13 | 当然,这不是绝对的,当我们以fastcgi模式运行php的时候,往往需要手工通过 14 | 命令来启动来启动php后端服务。 15 | 16 | ## links 17 | * [目录]() 18 | * 下一节: [让我们从SAPI开始](<1.1.md>) 19 | 20 | -------------------------------------------------------------------------------- /10.1.md: -------------------------------------------------------------------------------- 1 | # 10.1 PHP中的面向对象(一) 2 | 3 | zend_class_entry是内核中定义的一个结构体,是内核实现PHP语言中类与对象的一个非常基础、关键的结构类型。他就相当于我们定义的类的原型。 4 | 如果我们想获得一个名字为myclass的类该怎么做呢?首先我们定义一个zend_class_entry变量,并为它设置名字,最后注册到runtime中去。 5 | ````c 6 | zend_class_entry *myclass_ce; 7 | 8 | 9 | static zend_function_entry myclass_method[] = { 10 | { NULL, NULL, NULL } 11 | }; 12 | 13 | ZEND_MINIT_FUNCTION(sample3) 14 | { 15 | zend_class_entry ce; 16 | 17 | //"myclass"是这个类的名称。 18 | INIT_CLASS_ENTRY(ce, "myclass",myclass_method); 19 | myclass_ce = zend_register_internal_class(&ce TSRMLS_CC);//PHP7中取掉最后的TSRMLS_CC 20 | return SUCCESS; 21 | } 22 | 23 | ```` 24 | 这样我们便定义了一个类myclass,而且我们可以正常的在PHP语言中使用它,比如: 25 | ````php 26 | ) 35 | * 10.2 [定义一个类](<10.2.md>) 36 | 37 | -------------------------------------------------------------------------------- /10.3.md: -------------------------------------------------------------------------------- 1 | # 10.3 PHP中的面向对象(一) 2 | 3 | 定义一个接口还是很方便的,我先给出一个PHP语言中的形式。 4 | ````php 5 | name."\n"; 42 | } 43 | } 44 | 45 | $obj = new sample(); 46 | $obj->hello(); 47 | 48 | ```` 49 | 50 | 51 | ## links 52 | * 10.2 [定义一个类](<10.2.md>) 53 | * 10.4 [继承与实现接口](<10.4.md>) 54 | 55 | -------------------------------------------------------------------------------- /10.4.md: -------------------------------------------------------------------------------- 1 | # 10.4 PHP中的面向对象(一) 2 | 3 | 在定义一个类时往往会使其继承某个父类或者实现某个接口,在扩展中实现这个功能非常方便。下面我先给出PHP语言中的代码。 4 | ````php 5 | hello(); 24 | } 25 | } 26 | 27 | ```` 28 | 上面的代码我们已经非常熟悉了,它们在PHP扩展中的实现应该是这样的: 29 | ````c 30 | 31 | //三个zend_class_entry 32 | zend_class_entry *i_myinterface_ce,*parent_class_ce,*myclass_ce; 33 | 34 | //parent_class的hello方法 35 | ZEND_METHOD(parent_class,hello) 36 | { 37 | php_printf("hello world!\n"); 38 | } 39 | 40 | //myclass的call_hello方法 41 | //注意PHP7中zend_call_method_with_0_params的参数与php5及以前版本中不同 42 | // php5中第一个参数是指向指针的指针 php7中则是指针 43 | ZEND_METHOD(myclass,call_hello) 44 | { 45 | //这里涉及到如何调用对象的方法,详细内容下一章叙述 46 | zval *this_zval; 47 | this_zval = getThis(); 48 | zend_call_method_with_0_params(&this_zval,myclass_ce,NULL,"hello",NULL); 49 | //zend_call_method_with_0_params(this_zval,myclass_ce,NULL,"hello",NULL); //php7 50 | } 51 | 52 | //各自的zend_function_entry 53 | static zend_function_entry i_myinterface_method[]={ 54 | ZEND_ABSTRACT_ME(i_myinterface, hello, NULL) 55 | ZEND_FE_END 56 | }; 57 | 58 | static zend_function_entry parent_class_method[]={ 59 | ZEND_ME(parent_class,hello,NULL,ZEND_ACC_PUBLIC) 60 | ZEND_FE_END 61 | }; 62 | 63 | static zend_function_entry myclass_method[]={ 64 | ZEND_ME(myclass,call_hello,NULL,ZEND_ACC_PUBLIC) 65 | ZEND_FE_END 66 | }; 67 | 68 | //注意php7中zend_register_internal_class_ex与php5中的参数个数不同 php7中只有两个参数去掉了最后一个『可选』参数 69 | ZEND_MINIT_FUNCTION(test) 70 | { 71 | zend_class_entry ce,p_ce,i_ce; 72 | INIT_CLASS_ENTRY(i_ce,"i_myinterface",i_myinterface_method); 73 | i_myinterface_ce = zend_register_internal_interface(&i_ce TSRMLS_CC); 74 | 75 | 76 | //定义父类,最后使用zend_class_implements函数声明它实现的接口 77 | INIT_CLASS_ENTRY(p_ce,"parent_class",parent_class_method); 78 | parent_class_ce = zend_register_internal_class(&p_ce TSRMLS_CC); 79 | zend_class_implements(parent_class_ce TSRMLS_CC,1,i_myinterface_ce); 80 | 81 | //定义子类,使用zend_register_internal_class_ex函数 82 | INIT_CLASS_ENTRY(ce,"myclass",myclass_method); 83 | 84 | //最后一个参数一般指定为NULL,否则如果指定最后一个参数时,如果找不到此类则此函数会返回NULL 85 | myclass_ce = zend_register_internal_class_ex(&ce,parent_class_ce,"parent_class" TSRMLS_CC);//php5 86 | 87 | //注意该函数在php7中只有两个参数 88 | myclass_ce = zend_register_internal_class_ex(&ce,parent_class_ce );//php7 89 | 90 | //注意:ZEND_ACC_FINAL是用来修饰方法的,而ZEND_ACC_FINAL_CLASS是用来修饰类的 91 | myclass_ce->ce_flags |= ZEND_ACC_FINAL_CLASS; 92 | return SUCCESS; 93 | } 94 | 95 | ```` 96 | 这样,当我们在PHP语言中进行如下操作时,便会得到预期的输出: 97 | ````php 98 | hello(); 101 | /* 102 | 输出内容: 103 | walu@walu-ThinkPad-Edge:/cnan/program/php-5.3.6/ext/test$ php test.php 104 | hello world! 105 | */ 106 | 107 | ```` 108 | 这里的ZEND_ABSTRACT_ME()宏函数比较特殊,它会声明一个abstract public类型的函数,这个函数不需要我们实现,因此也就不需要相应的ZEND_METHOD(i_myinterface,hello){...}的实现。一般来说,一个接口是不能设计出某个非public类型的方法的,因为接口暴露给使用者的都应该是一些公开的信息。不过如果你非要这么设计,那也不是办不到,只要别用ZEND_ABSTRACT_ME()宏函数就行了,而用它的底层实现ZEND_FN()宏函数 109 | ````c 110 | //它可以对应) 137 | * 10.5 [小结](<10.5.md>) 138 | 139 | -------------------------------------------------------------------------------- /10.5.md: -------------------------------------------------------------------------------- 1 | # 10.5 PHP中的面向对象(一) 2 | 3 | PHP7中的异常与PHP5略有不同,PHP7中新添加了一类Error。所以PHP7即可以抛出异常,又可以抛出错误。 4 | 5 | API 6 | ``` 7 | ZEND_API ZEND_COLD zend_object *zend_throw_exception( 8 | zend_class_entry *exception_ce, 9 | const char *message, 10 | zend_long code); 11 | 12 | ZEND_API zend_class_entry *zend_exception_get_default(void) 13 | { 14 | return zend_ce_exception; 15 | } 16 | /* }}} */ 17 | 18 | /* {{{ Deprecated - Use zend_ce_error_exception directly instead */ 19 | ZEND_API zend_class_entry *zend_get_error_exception(void) 20 | { 21 | return zend_ce_error_exception; 22 | } 23 | 24 | ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...); 25 | 26 | ``` 27 | 28 | ### 示例: 抛出异常 29 | 30 | ```php 31 | function test($val) 32 | { 33 | throw new Exception("only for test exception"); 34 | //'do nothing'; 35 | } 36 | 37 | ``` 38 | 39 | ```c 40 | #include "zend_exceptions.h"//需要引入这个 41 | 42 | PHP_METHOD(TEST_NAME, test) 43 | { 44 | zend_throw_exception(zend_ce_exception, "only for test exception", 0 ); 45 | //zend_throw_exception(NULL, "only for test", 0 );//同上 46 | } 47 | 48 | ``` 49 | 50 | ### 示例抛出错误 51 | 52 | 53 | ```php 54 | function test($val) 55 | { 56 | throw new Error("only for test error"); 57 | //'do nothing'; 58 | } 59 | 60 | ``` 61 | 62 | ```c 63 | 64 | PHP_METHOD(TEST_NAME, test) 65 | { 66 | zend_throw_error(zend_ce_error, "only for test error" ); 67 | //zend_throw_error(NULL, "only for test erro");//这里的第一个参数可以指定为NULL,默认会使用zend_ce_error 68 | } 69 | 70 | ``` 71 | 开发者亦可抛出自定义的异常类如yaf中: 72 | ``` 73 | void yaf_throw_exception(long code, char *message) { 74 | zend_class_entry *base_exception = yaf_exception_ce; 75 | 76 | if ((code & YAF_ERR_BASE) == YAF_ERR_BASE 77 | && yaf_buildin_exceptions[YAF_EXCEPTION_OFFSET(code)]) { 78 | base_exception = yaf_buildin_exceptions[YAF_EXCEPTION_OFFSET(code)]; 79 | } 80 | 81 | zend_throw_exception(base_exception, message, code); 82 | } 83 | ``` -------------------------------------------------------------------------------- /10.6.md: -------------------------------------------------------------------------------- 1 | # 10.5 PHP中的面向对象(一) 2 | 3 | 这一章是我自己写的,如果有什么错误,还请大家指正。这章主要介绍了类与接口的定义,在下一章将看一下如何对类进行操作,比如调用方法、修改属性等。 4 | 5 | 6 | ## links 7 | * 10.4 [抛出异常|错误](<10.5.md>) 8 | * 11 [PHP中的面向对象(二)](<11.md>) 9 | 10 | -------------------------------------------------------------------------------- /10.md: -------------------------------------------------------------------------------- 1 | # 10 PHP中的面向对象(一) 2 | 3 | * [1. zend_class_entry](10.1.md) 4 | * [2. 定义一个类](10.2.md) 5 | * [3. 定义一个接口](10.3.md) 6 | * [4. 类的继承与接口的实现](10.4.md) 7 | * [5. 抛出异常|错误](10.5.md) 8 | * [6. 小结](10.6.md) 9 | 10 | 面向对象的概念这里就不再叙述了。原书中把这一部分的知识分开到PHP4和PHP5中来讲的,这里我做了大幅的调整,几乎是进行了重写。前一部分主要介绍了如何定义类、接口等一些声明类的操作。后一部分主要介绍了对象的使用等一些对实例的操作。 11 | 12 | 13 | ## links 14 | * 9.4 [第九章小结](<9.4.md>) 15 | * 10.1 [zend_class_entry](<10.1.md>) 16 | 17 | -------------------------------------------------------------------------------- /11.1.md: -------------------------------------------------------------------------------- 1 | # 11.1 PHP中的面向对象(二) 2 | 3 | 为了操作一个对象,我们需要先获取这个对象的实例,而这肯定会涉及调用对象的构造方法。有关如何在扩展中调用PHP的函数与对象的方法这里不展开描述了。 4 | 首先我们先了解下一个object在PHP内核中到底是如何实现的。 5 | ````c 6 | typedef struct _zend_object_value { 7 | zend_object_handle handle; 8 | zend_object_handlers *handlers; 9 | } zend_object_value; 10 | 11 | //此外再回顾一下zval的值value的结构。 12 | typedef union _zvalue_value { 13 | long lval; /* long value */ 14 | double dval; /* double value */ 15 | struct { 16 | char *val; 17 | int len; 18 | } str; 19 | HashTable *ht; /* hash table value */ 20 | zend_object_value obj; 21 | } zvalue_value; 22 | 23 | ```` 24 | 25 | 26 | 如果我们有一个zval *tmp,那么tmp->value.obj来访问到最终保存对象实例的zend_object_value结构体,它包含两个成员: 27 |
    28 |
  • zend_object_handle handle:最终实现是一个unsigned int值,Zend会把每个对象放进数组里,这个handle就是此实例的索引。所以我们在把对象当作参数传递时,只不过是传递的handle罢了,这样对性能有利,同时也是对象的引用机制的原理。
  • 29 |
  • zend_object_handlers *handlers:这个里面是一组函数指针,我们可以通过它来对对象进行一些操作,比如:添加引用、获取属性等。此结构体在Zend/zend_object_handlers.h里定义。
  • 30 |
31 | 下面我给出这个类的PHP语言实现,让我们在扩展中实现它,并生成它。 32 | ````php 33 | hello(); 51 | } 52 | 53 | ```` 54 | 下面我们在扩展中实现以上test_call函数。 55 | ````c 56 | zend_class_entry *baby_ce; 57 | 58 | ZEND_FUNCTION(test_call) 59 | { 60 | zval *obj; 61 | MAKE_STD_ZVAL(obj); 62 | object_init_ex(obj, baby_ce); 63 | 64 | //如果确认此类没有构造函数就不用调用了。 65 | walu_call_user_function(NULL, obj, "__construct", ""); 66 | 67 | walu_call_user_function(NULL, obj, "hello", ""); 68 | zval_ptr_dtor(&obj); 69 | return; 70 | } 71 | 72 | ZEND_METHOD(baby, __construct) 73 | { 74 | printf("a new baby!\n"); 75 | } 76 | 77 | ZEND_METHOD(baby, hello) 78 | { 79 | printf("hello world!!!!!\n"); 80 | } 81 | 82 | static zend_function_entry baby_method[]={ 83 | ZEND_ME(baby, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) 84 | ZEND_ME(baby, hello, NULL, ZEND_ACC_PUBLIC) 85 | {NULL, NULL, NULL} 86 | }; 87 | 88 | ZEND_MINIT_FUNCTION(test) 89 | { 90 | zend_class_entry ce; 91 | INIT_CLASS_ENTRY(ce, "baby", baby_method); 92 | baby_ce = zend_register_internal_class(&ce TSRMLS_CC); 93 | return SUCCESS; 94 | } 95 | 96 | ```` 97 | 98 | 99 | 100 | (php7) 101 | 102 | ````c 103 | typedef struct _zval_struct zval; 104 | typedef struct _zend_object zend_object; 105 | 106 | struct _zend_object { 107 | zend_refcounted_h gc; 108 | uint32_t handle; // TODO: may be removed ??? 109 | zend_class_entry *ce; 110 | const zend_object_handlers *handlers;//指向下面的std_object_handlers或是其复本 111 | HashTable *properties; 112 | zval properties_table[1]; 113 | }; 114 | 115 | typedef union _zend_value { 116 | zend_long lval; /* long value */ 117 | double dval; /* double value */ 118 | zend_refcounted *counted; 119 | zend_string *str; 120 | zend_array *arr; 121 | zend_object *obj; 122 | zend_resource *res; 123 | zend_reference *ref; 124 | zend_ast_ref *ast; 125 | zval *zv; 126 | void *ptr; 127 | zend_class_entry *ce; 128 | zend_function *func; 129 | struct { 130 | uint32_t w1; 131 | uint32_t w2; 132 | } ww; 133 | } zend_value; 134 | 135 | struct _zval_struct { 136 | zend_value value; /* value */ 137 | union { 138 | struct { 139 | ZEND_ENDIAN_LOHI_4( 140 | zend_uchar type, /* active type */ 141 | zend_uchar type_flags, 142 | zend_uchar const_flags, 143 | zend_uchar reserved) /* call info for EX(This) */ 144 | } v; 145 | uint32_t type_info; 146 | } u1; 147 | union { 148 | uint32_t next; /* hash collision chain */ 149 | uint32_t cache_slot; /* literal cache slot */ 150 | uint32_t lineno; /* line number (for ast nodes) */ 151 | uint32_t num_args; /* arguments number for EX(This) */ 152 | uint32_t fe_pos; /* foreach position */ 153 | uint32_t fe_iter_idx; /* foreach iterator index */ 154 | uint32_t access_flags; /* class constant access flags */ 155 | uint32_t property_guard; /* single property guard */ 156 | uint32_t extra; /* not further specified */ 157 | } u2; 158 | }; 159 | 160 | 161 | //其中 162 | 163 | ZEND_API zend_object_handlers std_object_handlers = { 164 | 0, /* offset */ 165 | 166 | zend_object_std_dtor, /* free_obj */ 167 | zend_objects_destroy_object, /* dtor_obj */ 168 | zend_objects_clone_obj, /* clone_obj */ 169 | 170 | zend_std_read_property, /* read_property */ 171 | zend_std_write_property, /* write_property */ 172 | zend_std_read_dimension, /* read_dimension */ 173 | zend_std_write_dimension, /* write_dimension */ 174 | zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */ 175 | NULL, /* get */ 176 | NULL, /* set */ 177 | zend_std_has_property, /* has_property */ 178 | zend_std_unset_property, /* unset_property */ 179 | zend_std_has_dimension, /* has_dimension */ 180 | zend_std_unset_dimension, /* unset_dimension */ 181 | zend_std_get_properties, /* get_properties */ 182 | zend_std_get_method, /* get_method */ 183 | NULL, /* call_method */ 184 | zend_std_get_constructor, /* get_constructor */ 185 | zend_std_object_get_class_name, /* get_class_name */ 186 | zend_std_compare_objects, /* compare_objects */ 187 | zend_std_cast_object_tostring, /* cast_object */ 188 | NULL, /* count_elements */ 189 | zend_std_get_debug_info, /* get_debug_info */ 190 | zend_std_get_closure, /* get_closure */ 191 | zend_std_get_gc, /* get_gc */ 192 | NULL, /* do_operation */ 193 | NULL, /* compare */ 194 | }; 195 | 196 | 197 | ZEND_API zend_object_handlers *zend_get_std_object_handlers(void) 198 | { 199 | return &std_object_handlers; 200 | } 201 | 202 | 203 | ```` 204 | 205 | php7中类的对象实例的实现更有简单,只需要使用zval *obj; ...... obj->value.obj即可取得obj对象实例 206 | 207 | php7中实现上述代码: 208 | 209 | ````c 210 | zend_class_entry *baby_ce; 211 | 212 | PHP_FUNCTION(test_call) 213 | { 214 | zval obj; 215 | object_init_ex(&obj, baby_ce); 216 | 217 | //如果确认此类没有构造函数就不用调用了。 218 | zend_call_method_with_0_params( &obj, baby_ce, NULL, "__construct", NULL ); 219 | zend_call_method_with_0_params( &obj, baby_ce, NULL, "hello", NULL ); 220 | zval_ptr_dtor(&obj); 221 | 222 | php_printf( "hello\r\n" ); 223 | return; 224 | } 225 | 226 | PHP_METHOD(baby, __construct) 227 | { 228 | php_printf("a new baby!\n"); 229 | } 230 | 231 | PHP_METHOD(baby, hello) 232 | { 233 | php_printf("hello world!!!!!\n"); 234 | } 235 | 236 | static zend_function_entry baby_method[]={ 237 | PHP_ME(baby, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) 238 | PHP_ME(baby, hello, NULL, ZEND_ACC_PUBLIC) 239 | PHP_FE_END 240 | }; 241 | 242 | ```` 243 | 244 | ## links 245 | * 11 [PHP中的面向对象(二)](<11.md>) 246 | * 11.2 [读写对象的属性](<11.2.md>) 247 | 248 | -------------------------------------------------------------------------------- /11.3.md: -------------------------------------------------------------------------------- 1 | # 11.3 PHP中的面向对象(二) 2 | 3 | 使用多个类进行编程,在多个文件中进行编程 4 | 5 | 6 | 在上一节里我们已经看了下如何操作一个对象的方法,这一节主要描述与对象属性有关的东西。有关如何对它进行定义的操作我们已经在上一章中描述过了,这里不再叙述,只讲对其的操作。 7 | ## 读取对象的属性 8 | ````c 9 | ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, char *name, int name_length, zend_bool silent TSRMLS_DC); 10 | ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_bool silent, zval *rv) ;//php7 11 | 12 | ZEND_API zval *zend_read_static_property(zend_class_entry *scope, char *name, int name_length, zend_bool silent TSRMLS_DC); 13 | ZEND_API zval *zend_read_static_property(zend_class_entry *scope, const char *name, size_t name_length, zend_bool silent);//php7 14 | 15 | ```` 16 | zend_read_property函数用于读取对象的属性,而zend_read_static_property则用于读取静态属性。可以看出,静态属性是直接保存在类上的,与具体的对象无关。 17 | silent参数: 18 |
    19 |
  • 0: 如果属性不存在,则抛出一个notice错误。
  • 20 |
  • 1: 如果属性不存在,不报错。
  • 21 |
22 | 如果所查的属性不存在,那么此函数将返回IS_NULL类型的zval。 23 | ### 更新对象的属性 24 | ````c 25 | ZEND_API void zend_update_property(zend_class_entry *scope, zval *object, char *name, int name_length, zval *value TSRMLS_DC); 26 | ZEND_API void zend_update_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zval *value);//php7 27 | 28 | ZEND_API int zend_update_static_property(zend_class_entry *scope, char *name, int name_length, zval *value TSRMLS_DC); 29 | ZEND_API int zend_update_static_property(zend_class_entry *scope, const char *name, size_t name_length, zval *value);//php7 30 | 31 | ```` 32 | zend_update_property用来更新对象的属性,zend_update_static_property用来更新类的静态属性。如果对象或者类中没有相关的属性,函数将自动的添加上。 33 | ### 读写对象与类属性的实例 34 | 假设我们已经在扩展中定义好下面的类: 35 | ````php 36 | class baby 37 | { 38 | public $age; 39 | public static $area; 40 | 41 | public function __construct($age, $area) 42 | { 43 | $this->age = $age; 44 | self::$area = $area; 45 | 46 | var_dump($this->age, self::$area); 47 | } 48 | } 49 | 50 | PHP_METHOD(baby, __construct) 51 | { 52 | zval *age, *area, rv; 53 | zend_class_entry *ce; 54 | ce = Z_OBJCE_P(getThis()); 55 | 56 | if( zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &age, &area) == FAILURE ) 57 | { 58 | printf("Error\n"); 59 | RETURN_NULL(); 60 | } 61 | zend_update_property(ce, getThis(), "age", sizeof("age")-1, age); 62 | zend_update_static_property(ce, "area", sizeof("area")-1, area ); 63 | 64 | age = NULL; 65 | area = NULL; 66 | 67 | age = zend_read_property(ce, getThis(), "age", sizeof("age")-1, 0, &rv ); 68 | php_var_dump(age, 1); 69 | 70 | area = zend_read_static_property(ce, "area", sizeof("area")-1, 0, &rv ); 71 | php_var_dump(area, 1); 72 | 73 | } 74 | 75 | //(php7) 76 | PHP_METHOD(baby, __construct) 77 | { 78 | zval *age, *area; 79 | zend_class_entry *ce; 80 | ce = Z_OBJCE_P(getThis()); 81 | zval rv; 82 | 83 | if( zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &age, &area) == FAILURE ) 84 | { 85 | php_printf("Error\n"); 86 | RETURN_NULL(); 87 | } 88 | zend_update_property(ce, getThis(), "age", sizeof("age")-1, age ); 89 | zend_update_static_property(ce, "area", sizeof("area")-1, area ); 90 | 91 | age = NULL; 92 | area = NULL; 93 | 94 | age = zend_read_property(ce, getThis(), "age", sizeof("age")-1, 0, &rv ); 95 | php_var_dump(&age, 1 ); 96 | 97 | area = zend_read_static_property(ce, "area", sizeof("area")-1, 0 ); 98 | php_var_dump(&area, 1); 99 | 100 | } 101 | ```` 102 |
103 | 感谢 [@看你取的破名](http://weibo.com/taokuizu) 发现的错误。 104 |
105 | ### 一些其它的快捷函数 106 | #### 更新对象与类的属性 107 | ````c 108 | ZEND_API void zend_update_property_null(zend_class_entry *scope, zval *object, char *name, int name_length TSRMLS_DC); 109 | ZEND_API void zend_update_property_bool(zend_class_entry *scope, zval *object, char *name, int name_length, long value TSRMLS_DC); 110 | ZEND_API void zend_update_property_long(zend_class_entry *scope, zval *object, char *name, int name_length, long value TSRMLS_DC); 111 | ZEND_API void zend_update_property_double(zend_class_entry *scope, zval *object, char *name, int name_length, double value TSRMLS_DC); 112 | ZEND_API void zend_update_property_string(zend_class_entry *scope, zval *object, char *name, int name_length, const char *value TSRMLS_DC); 113 | ZEND_API void zend_update_property_stringl(zend_class_entry *scope, zval *object, char *name, int name_length, const char *value, int value_length TSRMLS_DC); 114 | 115 | 116 | ZEND_API int zend_update_static_property_null(zend_class_entry *scope, char *name, int name_length TSRMLS_DC); 117 | ZEND_API int zend_update_static_property_bool(zend_class_entry *scope, char *name, int name_length, long value TSRMLS_DC); 118 | ZEND_API int zend_update_static_property_long(zend_class_entry *scope, char *name, int name_length, long value TSRMLS_DC); 119 | ZEND_API int zend_update_static_property_double(zend_class_entry *scope, char *name, int name_length, double value TSRMLS_DC); 120 | ZEND_API int zend_update_static_property_string(zend_class_entry *scope, char *name, int name_length, const char *value TSRMLS_DC); 121 | ZEND_API int zend_update_static_property_stringl(zend_class_entry *scope, char *name, int name_length, const char *value, int value_length TSRMLS_DC); 122 | 123 | ```` 124 | 125 | (php7) 126 | 127 | ````c 128 | ZEND_API void zend_update_property_ex(zend_class_entry *scope, zval *object, zend_string *name, zval *value); 129 | ZEND_API void zend_update_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zval *value); 130 | ZEND_API void zend_update_property_null(zend_class_entry *scope, zval *object, const char *name, size_t name_length); 131 | ZEND_API void zend_update_property_bool(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_long value); 132 | ZEND_API void zend_update_property_long(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_long value); 133 | ZEND_API void zend_update_property_double(zend_class_entry *scope, zval *object, const char *name, size_t name_length, double value); 134 | ZEND_API void zend_update_property_str(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_string *value); 135 | ZEND_API void zend_update_property_string(zend_class_entry *scope, zval *object, const char *name, size_t name_length, const char *value); 136 | ZEND_API void zend_update_property_stringl(zend_class_entry *scope, zval *object, const char *name, size_t name_length, const char *value, size_t value_length); 137 | ZEND_API void zend_unset_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length); 138 | 139 | ZEND_API int zend_update_static_property(zend_class_entry *scope, const char *name, size_t name_length, zval *value); 140 | ZEND_API int zend_update_static_property_null(zend_class_entry *scope, const char *name, size_t name_length); 141 | ZEND_API int zend_update_static_property_bool(zend_class_entry *scope, const char *name, size_t name_length, zend_long value); 142 | ZEND_API int zend_update_static_property_long(zend_class_entry *scope, const char *name, size_t name_length, zend_long value); 143 | ZEND_API int zend_update_static_property_double(zend_class_entry *scope, const char *name, size_t name_length, double value); 144 | ZEND_API int zend_update_static_property_string(zend_class_entry *scope, const char *name, size_t name_length, const char *value); 145 | ZEND_API int zend_update_static_property_stringl(zend_class_entry *scope, const char *name, size_t name_length, const char *value, size_t value_length); 146 | 147 | ```` 148 | 149 | 150 | ## links 151 | * 11.1 [生成对象的实例](<11.1.md>) 152 | * 11.3 [小结](<11.3.md>) 153 | 154 | -------------------------------------------------------------------------------- /11.4.md: -------------------------------------------------------------------------------- 1 | # 11.3 PHP中的面向对象(二) 2 | 3 | 4 | 有关面向对象的资料实在是太多了,我也是才学而已,没有办法给出非常系统、完整的阐述,但以后我会陆陆续续的在博客里写出来的。此外,强烈建议大家看看php官方的这篇wiki。 5 | [internals:engine:objects](https://wiki.php.net/internals/engine/objects) 6 | 7 | 8 | ## links 9 | * 11.2 [读写对象的属性](<11.2.md>) 10 | * 12 [启动与终止的那点事](<12.md>) 11 | 12 | -------------------------------------------------------------------------------- /11.md: -------------------------------------------------------------------------------- 1 | # 11 PHP中的面向对象(二) 2 | 3 | TBD 4 | 5 | * [1. 生成对象的实例与调用方法](11.1.md) 6 | * [2. 读写对象的属性](11.2.md) 7 | * [3. 小结](11.3.md) 8 | 9 | 上一章里,我们看了一下如何在PHP扩展里定义类与接口,那这一章里我们将入手学习一下如何在PHP扩展中操作类的实例————对象。 10 | PHP语言中的面向对象其实是分为三个部分来实现的,class、object、refrence。class就是我们所说的类,可以直观的理解为前面章节中所描述的zend_class_entry。object就是实际的对象。每一个zval并不直接包含具体的object,而是通过一个索引--refrence与其联系。也就是说,每个class都有很多个object实例,并把他们统一的放在一个数组里,每个zval只要记住自己相应的key就行了。如此一来,我们在传递zval时候,实际上传递的是一个索引,而不是内存中具体的对象数据。 11 | 12 | 13 | ## links 14 | * 10.5 [小结](<10.5.md>) 15 | * 11.1 [生成对象的实例](<11.1.md>) 16 | 17 | -------------------------------------------------------------------------------- /12.1.md: -------------------------------------------------------------------------------- 1 | # 12.1 关于生命周期 2 | 3 | 除了在上一节说到的4个函数,还有2个函数只用于处理单个线程的启动和关闭,他们只作用于线程环境。 4 | 5 | 首先,建立一个基本扩展,根据你PHP源码树使用下面几个源文件。 6 | 7 | config.m4(php5) 8 | ````c 9 | PHP_ARG_ENABLE(sample4, 10 | [Whether to enable the "sample4" extension], 11 | [ enable-sample4 Enable "sample4" extension support]) 12 | if test $PHP_SAMPLE4 != "no"; then 13 | PHP_SUBST(SAMPLE4_SHARED_LIBADD) 14 | PHP_NEW_EXTENSION(sample4, sample4.c, $ext_shared) 15 | fi 16 | ```` 17 | 18 | config.m4(php7) 19 | ````c 20 | 21 | PHP_ARG_ENABLE(sample4, whether to enable sample4 support, 22 | Make sure that the comment is aligned: 23 | [ --enable-sample4 Enable sample4 support]) 24 | 25 | if test "$PHP_SAMPLE4" != "no"; then 26 | PHP_NEW_EXTENSION(sample4, sample4.c sample4_http_util.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) 27 | fi 28 | 29 | ```` 30 | 31 | php_sample4.h 32 | ````c 33 | #ifndef PHP_SAMPLE4_H 34 | /* Prevent double inclusion */ 35 | #define PHP_SAMPLE4_H 36 | 37 | /* Define Extension Properties */ 38 | #define PHP_SAMPLE4_EXTNAME 39 | #define PHP_SAMPLE4_EXTVER 40 | 41 | /* Import configure options when building outside of the PHP source tree */ 42 | #ifdef HAVE_CONFIG_H 43 | #include "config.h" 44 | #endif 45 | 46 | /* Include PHP Standard Header */ 47 | #include "php.h" 48 | 49 | /* Define the entry point symbol 50 | * Zend will use when loading this module 51 | */ 52 | extern zend_module_entry sample4_module_entry; 53 | #define phpext_sample4_ptr &sample4_module_entry 54 | #endif /* PHP_SAMPLE4_H */ 55 | ```` 56 | 57 | sample4.c 58 | ````c 59 | #include "php_sample4.h" 60 | #include "ext/standard/info.h" 61 | 62 | static function_entry php_sample4_functions[] = { 63 | { NULL, NULL, NULL } 64 | }; 65 | 66 | PHP_MINIT_FUNCTION(sample4) 67 | { 68 | return SUCCESS; 69 | } 70 | 71 | PHP_MSHUTDOWN_FUNCTION(sample4) { 72 | return SUCCESS; 73 | } 74 | 75 | PHP_RINIT_FUNCTION(sample4) { 76 | return SUCCESS; 77 | } 78 | PHP_RSHUTDOWN_FUNCTION(sample4) { 79 | return SUCCESS; 80 | } 81 | 82 | PHP_MINFO_FUNCTION(sample4) { 83 | } 84 | 85 | zend_module_entry sample4_module_entry = { 86 | #if ZEND_MODULE_API_NO >= 20010901 87 | STANDARD_MODULE_HEADER, 88 | #endif 89 | PHP_SAMPLE4_EXTNAME, 90 | php_sample4_functions, 91 | PHP_MINIT(sample4), 92 | PHP_MSHUTDOWN(sample4), 93 | PHP_RINIT(sample4), 94 | PHP_RSHUTDOWN(sample4), 95 | PHP_MINFO(sample4), 96 | #if ZEND_MODULE_API_NO >= 20010901 97 | PHP_SAMPLE4_EXTVER, 98 | #endif 99 | STANDARD_MODULE_PROPERTIES 100 | }; 101 | 102 | #ifdef COMPILE_DL_SAMPLE4 103 | ZEND_GET_MODULE(sample4) 104 | #endif 105 | ```` 106 | 107 | 注意:每个启动或者关闭的方法在return SUCCESS时退出。如果其中任何的函数return FAILURE,PHP为了避免出现严重问题而将请求中止。 108 | 109 | 现在你应该对MINIT很熟悉了吧,它会在一个模块第一次加载到进程空间的时候被触发。 110 | 111 | 对于多进程的SAPIS(Apache1 & Apache2-prefork),多个web server进程会fork出多个mod_php实例。每个mod_php实例都必须加载属于这个实例 112 | 的扩展模块,这意味着MINIT函数会被执行多次。但是,它在每个进程空间中只会执行一次。 113 | 114 | 当一个模块被卸载,MSHUTDOWN会被调用,它可以使用该模块的任何资源,比如被占用的内存可能会被释放。 115 | 116 | 这里要注意个特性, 某些PHP的SAPI中, 比如Apache Prefork, PHP是作为一个动态库被加载到Apache中的, 而从Apache 1.3以后(如果我没记错的话), Apache做了一个优化, 优化的结果就是首先执行各个动态模块的模块初始化工作, 然后才做fork, 派生Worker子进程, 所以反应到这里, 有的时候会出现MINIT只执行一次, 而MSHUTDOWN会执行多次的现象. 117 | 118 | 理论上来说,你可以在MSHUTDOWN中跳过一些资源的清理工作,然而在APACHE 1.3上的时候,你会发现一个有趣的事情,apache会载入mod_php, 119 | 并且会执行所有的MINIT方法,然后立刻卸载mod_php来触发MSHUTDOWN,接着再次装入,在没有执行MSHUTDOWN的时候,最初使用MINIT加载的 120 | 资源将被泄露和浪费。 121 | 122 | 在多线程的SAPIS中,有时需要为每个线程分配自己独立的资源或跟踪每个请求的计数器。对于这些特殊情况,在每一个线程钩子中,允许额外的启动和关闭 123 | 要执行的方法。通常情况下,当多进程的SAPIS(Apache2-worker)启动时,它会创建出十几个或者更多的线程,以便能够处理多个并发请求。 124 | 125 | 任何可以请求之间共享,但不能由多个线程在同一进程空间同时访问的资源,通常分配在线程的构造和析构方法中以免发生冲突。比如可能包括在 126 | EG( persistent_list ) HashTable中的持久性资源,因为他们往往​​包括网络或文件资源。 127 | 128 | ## links 129 | * [目录]() 130 | * 上一节: [启动与终止的那点事](<12.md>) 131 | * 下一节: [MINFO与phpinfo](<12.2.md>) 132 | -------------------------------------------------------------------------------- /12.2.md: -------------------------------------------------------------------------------- 1 | # 12.2 MINFO与phpinfo 2 | 3 | 如果你并不打算做出一个只有你自己使用的扩展,那么你可以需要告诉用户一些关于你的扩展信息。比如:其环境和特定版本的可用功能、版本信息、 4 | 作者信息以便你在发生问题的时候可以寻求帮助、甚至可以加上一个LOGO等等。 5 | 6 | 如果你仔细看过phpinfo()或者 php -i 的输出,相信你已经注意到,所有这些信息会组合成一个格式良好的、易于解析输出。 你的扩展可以轻松地通过 7 | MINFO (模块信息)来添加这些块,看个例子: 8 | 9 | ````c 10 | PHP_MINFO_FUNCTION(sample4) { 11 | php_info_print_table_start(); 12 | php_info_print_table_row(2, "Sample4 Module", "enabled"); 13 | php_info_print_table_row(2, "version", PHP_SAMPLE4_EXTVER); 14 | php_info_print_table_end(); 15 | } 16 | ```` 17 | 18 | 通过使用这些包装的功能,你的模块的信息将被自动包裹在HTML标签中从一个网络服务器SAPI (如CGI时,IIS , APACHE,等等)输出,或格式化使 19 | 用CLI使用时,输出明文和换行符。 20 | 21 | 下面我们来介绍一下php_info_*()系列的函数: 22 | 23 | ````c 24 | char *php_info_html_esc(char *str TSRMLS_DC) 25 | ```` 26 | 这个函数是php_escape_html_entities()的一个封装,htmlentites() 函数的底层实现。该函数返回的字符串通过emalloc()创建,并在使用后必须使用 27 | efree()函数释放掉。 28 | 29 | ````c 30 | void php_info_print_table_start(void) 31 | void php_info_print_table_end(void) 32 | ```` 33 | 输出开/关表格式所需的标签。HTML输出是与CLI输出一样,表现为一个简单的换行。 34 | 35 | ````c 36 | void php_info_print_table_header(int cols, ...) 37 | void php_info_print_table_colspan_header(int cols, char *header) 38 | ```` 39 | 输出表头行。第一个函数在可变参数列表中的char *元素外面的每一列都会输出一对th标签,第二个函数会在指定列数外面输出一对th标签。 40 | 41 | ````c 42 | void php_info_print_table_row(int cols, ...) 43 | void php_info_print_table_row_ex(int cols, char *class, ...) 44 | ```` 45 | 第一个函数在可变参数列表中的char *元素外面的每一行都会输出一对td标签,第二个函数会在指定列数外面输出一对td标签。当不在HTML中 46 | 输出的时候,两个函数将没有任何差别。 47 | 48 | ````c 49 | void php_info_print_hr(void) 50 | ```` 51 | 这种函数将在HTML中输出一个br标签,或者一个表示行开始和结束的水平线 52 | 53 | 我们常用的PHPWRITE()和php_printf()函数可以在在MINFO函数中使用,你应该注意正确的信息输出取决于当前的SAPI判断是用纯文本还是HTML的方式输出 54 | 要做到这一点,只需要检查sapi_module结构中的phpinfo_as_text属性,例子如下: 55 | 56 | ````c 57 | PHP_MINFO_FUNCTION(sample4) { 58 | php_info_print_table_start(); 59 | php_info_print_table_row(2, "Sample4 Module", "enabled"); 60 | php_info_print_table_row(2, "version", PHP_SAMPLE4_EXTVER); 61 | if (sapi_module.phpinfo_as_text) { 62 | /* No HTML for you */ 63 | php_info_print_table_row(2, "By", 64 | "Example Technologies\nhttp://www.example.com"); 65 | } else { 66 | /* HTMLified version */ 67 | php_printf("" 68 | "By" 69 | "" 70 | "" 72 | "" 73 | ""); 74 | php_info_print_table_end(); 75 | } 76 | } 77 | ```` 78 | 79 | 80 | 81 | ## links 82 | * [目录]() 83 | * 12.1 [关于生命周期](<12.1.md>) 84 | * 12.3 [常量](<12.3.md>) 85 | 86 | -------------------------------------------------------------------------------- /12.3.md: -------------------------------------------------------------------------------- 1 | # 12.3 常量 2 | 3 | 在脚本中使用扩展的一个方便之处是,人们可以改变自己定义的常量。你可以通过define()函数来定义一个常量。在内核中,我们将会使用REGISTER_*_CONSTANT()的 4 | 家族函数来使用常量。 5 | 6 | 对于你定义的大多数常量来说,你可能希望在程序初始化的时候便定义这些变量。你可能需要在MINIT函数: 7 | 8 | ````c 9 | PHP_MINIT_FUNCTION(sample4) { 10 | REGISTER_STRING_CONSTANT("SAMPLE4_VERSION", 11 | PHP_SAMPLE4_EXTVER, CONST_CS | CONST_PERSISTENT); 12 | return SUCCESS; 13 | } 14 | ```` 15 | 16 | 第一个参数是你要定义的这个常量的名字。在例子中,我们定义了一个名称为SAMPLE4_VERSION的常量。有一点很重要,这里要注意宏REGISTER_*_CONSTANT()的 17 | 使用,这些函数中为了确定常量的名称长度使用了sizeof()。这就意味着,常量的名称只能为文字,大家可以尝试使用一个char *的变量,这将导致sizeof计算出错误 18 | 的字符串长度。 19 | 20 | 接下来,我们来看看常量的值。在大多数情况下,它会是一个单一参数的类型,然而在STRINGL的版本中,你会看到在一些情况下会需要使用第二个参数来表明长度。 21 | 当注册string类型的常量时,字符串的值不会被复制到常量中,而仅仅是一个引用。这意味着,动态创建的字符串需要持久化和在shutdown的阶段被释放掉。 22 | 23 | 最后,在最后一个参数,你可以通过两个可以标识位的按位或组合传入。CONST_CS标识是否大小写敏感,一般情况下CONST_CS标识是默认使用的。对于一些特殊的 24 | 情况,比如TRUE,FALSE,NULL等等,这个参数将被省略。 25 | 26 | 在|后的标识位中的标识符说明了该常量的作用域和生命周期。当我们在MINIT中定义常量时,你可能需要在多个请求中使用这个常量,当你在RINIT中定义常量时,这个 27 | 常量会在当前请求结束的时候销毁。 28 | 29 | 下面列出的4个创建常量常用的函数,有一个共同需要注意的地方,常量名称一定要用文字而不是char *类型的变量。 30 | 31 | ````c 32 | REGISTER_LONG_CONSTANT(char *name, long lval, int flags) 33 | REGISTER_DOUBLE_CONSTANT(char *name, double dval, int flags) 34 | REGISTER_STRING_CONSTANT(char *name, char *value, int flags) 35 | REGISTER_STRINGL_CONSTANT(char *name,char *value, int value_len, int flags) 36 | ```` 37 | 38 | (php7) 39 | ````c 40 | #define REGISTER_NULL_CONSTANT(name, flags) zend_register_null_constant((name), sizeof(name)-1, (flags), module_number) 41 | #define REGISTER_BOOL_CONSTANT(name, bval, flags) zend_register_bool_constant((name), sizeof(name)-1, (bval), (flags), module_number) 42 | #define REGISTER_LONG_CONSTANT(name, lval, flags) zend_register_long_constant((name), sizeof(name)-1, (lval), (flags), module_number) 43 | #define REGISTER_DOUBLE_CONSTANT(name, dval, flags) zend_register_double_constant((name), sizeof(name)-1, (dval), (flags), module_number) 44 | #define REGISTER_STRING_CONSTANT(name, str, flags) zend_register_string_constant((name), sizeof(name)-1, (str), (flags), module_number) 45 | #define REGISTER_STRINGL_CONSTANT(name, str, len, flags) zend_register_stringl_constant((name), sizeof(name)-1, (str), (len), (flags), module_number) 46 | ```` 47 | 48 | 49 | 如果你没有办法提供文本类型的name,那么你可以尝试使用上面4个函数的底层函数去实现相同的效果: 50 | 51 | ````c 52 | void zend_register_long_constant(char *name, uint name_len, long lval, int flags, int module_number TSRMLS_DC) 53 | void zend_register_double_constant(char *name, uint name_len, double dval, int flags, int module_number TSRMLS_DC) 54 | void zend_register_string_constant(char *name, uint name_len, char *strval, int flags, int module_number TSRMLS_DC) 55 | void zend_register_stringl_constant(char *name, uint name_len, char *strval, uint strlen, int flags,int module_number TSRMLS_DC) 56 | ```` 57 | 58 | php7 59 | 60 | ````c 61 | 62 | ZEND_API void zend_register_bool_constant(const char *name, size_t name_len, zend_bool bval, int flags, int module_number); 63 | ZEND_API void zend_register_null_constant(const char *name, size_t name_len, int flags, int module_number); 64 | ZEND_API void zend_register_long_constant(const char *name, size_t name_len, zend_long lval, int flags, int module_number); 65 | ZEND_API void zend_register_double_constant(const char *name, size_t name_len, double dval, int flags, int module_number); 66 | ZEND_API void zend_register_string_constant(const char *name, size_t name_len, char *strval, int flags, int module_number); 67 | ZEND_API void zend_register_stringl_constant(const char *name, size_t name_len, char *strval, size_t strlen, int flags, int module_number); 68 | 69 | ```` 70 | 这样就可以由传入name_len而扩大了该族函数的使用范围(比如在循环中)。 71 | 72 | module_number是一个加载扩展或者卸载扩展时的标识。而你不需要关注它,它会自动加载到你扩展中的MINIT和RINIT中,所以在你用上面4个函数声明常量的时候, 73 | 你可以这样写: 74 | 75 | ````c 76 | PHP_MINIT_FUNCTION(sample4) { 77 | register_string_constant("SAMPLE4_VERSION", 78 | sizeof("SAMPLE4_VERSION"), 79 | PHP_SAMPLE4_EXTVER, 80 | CONST_CS | CONST_PERSISTENT, 81 | module_number TSRMLS_CC); 82 | return SUCCESS; 83 | } 84 | ```` 85 | 86 | 除了数组和对象外,其他变量你也可以用来注册一个常量,但是因为没有宏和ZEND API去支持这些声明,所以你必须手动声明一个常量,通过下面一个例子来了解一下: 87 | 88 | ````c 89 | void php_sample4_register_boolean_constant(char *name, uint len, 90 | zend_bool bval, int flags, int module_number TSRMLS_DC) 91 | { 92 | zend_constant c; 93 | 94 | ZVAL_BOOL(&c.value, bval); 95 | c.flags = CONST_CS | CONST_PERSISTENT; 96 | c.name = zend_strndup(name, len - 1); 97 | c.name_len = len; 98 | c.module_number = module_number; 99 | zend_register_constant(&c TSRMLS_CC); 100 | } 101 | 102 | ```` 103 | 104 | ````c 105 | 106 | void php_sample4_register_boolean_constant(char *name, uint len, 107 | zend_bool bval, int flags, int module_number ) 108 | { 109 | zend_constant c; 110 | 111 | ZVAL_BOOL(&c.value, bval); 112 | c.flags = flags; 113 | c.name = zend_string_init(name, len, flags & CONST_PERSISTENT); 114 | c.module_number = module_number; 115 | zend_register_constant(&c); 116 | } 117 | 118 | ```` 119 | ## links 120 | * [目录]() 121 | * 12.2 [MINFO与phpinfo](<12.2.md>) 122 | * 12.4 [PHP扩展中的全局变量](<12.4.md>) 123 | 124 | -------------------------------------------------------------------------------- /12.4.md: -------------------------------------------------------------------------------- 1 | # 12.4 PHP扩展中的全局变量 2 | 3 | TBD 4 | 5 | 这一章,我们将学会如何在PHP扩展中使用全局变量。 6 | 7 | 在扩展中定义全局变量 8 | 9 | 首先,我们需要在扩展的头文件中(默认是php_*.h)中定义所有的全局变量。举个例子,比如我们要定义一个无符号的long类型的全局变量,我们可以这样定义: 10 | 11 | ````c 12 | ZEND_BEGIN_MODULE_GLOBALS(sample4) 13 | unsigned long counter; 14 | ZEND_END_MODULE_GLOBALS(sample4) 15 | ```` 16 | 17 | 用ZEND_BEGIN_MODULE_GLOBALS和ZEND_END_MODULE_GLOBALS宏将定义的全局变量包起来。将上例中的宏展开后,是下面这个样子: 18 | 19 | ````c 20 | typedef struct _zend_sample4_globals { 21 | unsigned long counter; 22 | } zend_sample4_globals; 23 | ```` 24 | 25 | 如果你还有其他的全局变量需要定义,只需加在两个宏之间就可以了。接下来我们该在simple4.c中声明我们在头文件中定义的这些全局变量了: 26 | 27 | ````c 28 | ZEND_DECLARE_MODULE_GLOBALS(sample4); 29 | ```` 30 | 31 | 先来看一下这个宏 32 | 33 | (php7) 34 | 35 | ````c 36 | 37 | #ifdef ZTS 38 | 39 | #define ZEND_DECLARE_MODULE_GLOBALS(module_name) \ 40 | ts_rsrc_id module_name##_globals_id; 41 | #else 42 | #define ZEND_DECLARE_MODULE_GLOBALS(module_name) \ 43 | zend_##module_name##_globals module_name##_globals; 44 | #endif 45 | 46 | ```` 47 | 48 | 这个宏的内部实现取决于是否启用了线程安全,在非线程安全的环境下,如:Apache1,Apache2-prefork, CGI,CLI...会使用zend_sample4_globals结构来定义 49 | 全局变量: 50 | 51 | ````c 52 | zend_sample4_globals sample4_globals; 53 | ```` 54 | 55 | 我们可以直接通过sample4_globals.counter来获取计数器的值。在线程安全的版本中,另一种方法是声明一个整数: 56 | 57 | ````c 58 | int sample4_globals_id; 59 | ```` 60 | 61 | 填充这个ID就等于定义了扩展中的全局变量。根据其定义的信息,将为每个新线程的独立存储空间分配内存块。我们可以在MINIT中这样定义: 62 | 63 | ````c 64 | #ifdef ZTS 65 | ts_allocate_id( 66 | &sample4_globals_id, 67 | sizeof(zend_sample4_globals), 68 | NULL, NULL); 69 | #endif 70 | ```` 71 | 有一点需要注意这种方法需要包裹在#ifdef中,以防止它在没有启动Zend Thread Safety(ZTS)时执行。因为sample4_globals_id只能在多线程环境中使用。非线程 72 | 的版本用我们在前面提到的sample4_globals来声明全局变量。 73 | 74 | 线程中的初始化和关闭 75 | 76 | 在非线程的环境中,会将一个zend_sample4_globals结构的副本保存在指定进程中。你可以指定他的默认值,或者在MINIT或者RINIT中分配资源来初始化它。要记得 77 | 在对应的MSHUTDOWN或者RSHUTDOWN中及时释放这些资源。 78 | 79 | 然而在线程版本中,一个新的结构会在一个新线程spun的时候被分配。为了知道怎样初始化和关闭扩展中的全局变量,需要向ZE引擎提供回调函数。在前面我们在调用 80 | ts_allocate_id()的时候是是以NULL来作为这个值的,接下来我们添加2个一会需要在MINIT调用的方法: 81 | 82 | ````c 83 | static void php_sample4_globals_ctor(zend_sample4_globals *sample4_globals TSRMLS_DC) 84 | { 85 | /* Initialize a new zend_sample4_globals struct 86 | * During thread spin-up */ 87 | sample4_globals->counter = 0; 88 | } 89 | 90 | static void php_sample4_globals_dtor(zend_sample4_globals *sample4_globals TSRMLS_DC) 91 | { 92 | /* Any resources allocated during initialization 93 | * May be freed here */ 94 | } 95 | ```` 96 | 97 | 我们在启用和关闭的时候调用它们: 98 | 99 | ````c 100 | PHP_MINIT_FUNCTION(sample4) { 101 | REGISTER_STRING_CONSTANT("SAMPLE4_VERSION", PHP_SAMPLE4_EXTVER, CONST_CS | CONST_PERSISTENT); 102 | #ifdef ZTS 103 | ts_allocate_id(&sample4_globals_id, 104 | sizeof(zend_sample4_globals), 105 | (ts_allocate_ctor)php_sample4_globals_ctor, 106 | (ts_allocate_dtor)php_sample4_globals_dtor); 107 | #else 108 | php_sample4_globals_ctor(&sample4_globals TSRMLS_CC); 109 | #endif 110 | return SUCCESS; 111 | } 112 | 113 | PHP_MSHUTDOWN_FUNCTION(sample4) { 114 | #ifndef ZTS 115 | php_sample4_globals_dtor(&sample4_globals TSRMLS_CC); 116 | #endif 117 | return SUCCESS; 118 | } 119 | ```` 120 | 121 | 122 | php7中可以更简单的使用ZEND_INIT_MODULE_GLOBALS(soap, php_soap_init_globals, NULL)宏来实现: 123 | 124 | ````c 125 | 126 | PHP_MINIT_FUNCTION(sample4) { 127 | REGISTER_STRING_CONSTANT("SAMPLE4_VERSION", PHP_SAMPLE4_EXTVER, CONST_CS | CONST_PERSISTENT); 128 | 129 | ZEND_INIT_MODULE_GLOBALS(sample4, php_sample4_globals_ctor, php_sample4_globals_dtor); 130 | 131 | return SUCCESS; 132 | } 133 | 134 | ```` 135 | 现在我们已经知道如何在扩展中创建全局变量了,在不是ZTS的环境中,使用它们很简单,我们还来看前面定义的那个计数器的递增功能如何实现: 136 | 137 | ````c 138 | PHP_FUNCTION(sample4_counter) { 139 | RETURN_LONG(++sample4_globals.counter); 140 | } 141 | ```` 142 | 143 | 是不是看起来很简单,但是,在线程版本中将无法正常工作。那么我们来看看怎么在线程环境中完成这个功能吧: 144 | 145 | ````c 146 | PHP_FUNCTION(sample4_counter) 147 | { 148 | #ifdef ZTS 149 | RETURN_LONG(++TSRMG(sample4_globals_id, \ 150 | zend_sample4_globals*, counter)); 151 | #else 152 | /* non-ZTS */ 153 | RETURN_LONG(++sample4_globals.counter); 154 | #endif 155 | } 156 | ```` 157 | 158 | 159 | 看起来很丑对吗?想象一下,在你的整个代码库中,这些IFDEF指令在每一个线程安全的全局访问时穿插。它会看起来比Perl更糟糕!这就是为什么所有 160 | 的核心扩展,都有使用一个额外的宏观层抽象这种情况。我们可以在php_sample4.h中找到下面这段代码: 161 | 162 | ````c 163 | #ifdef ZTS 164 | #include "TSRM.h" 165 | #define SAMPLE4_G(v) TSRMG(sample4_globals_id, zend_sample4_globals*, v) 166 | #else 167 | #define SAMPLE4_G(v) (sample4_globals.v) 168 | #endif 169 | ```` 170 | 171 | php7中 172 | 173 | ````c 174 | #define HELLO_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(hello, v) 175 | ```` 176 | 177 | 我们再来看下ZEND_MODULE_GLOBALS_ACCESSOR宏 178 | 179 | ````c 180 | 181 | #ifdef ZTS 182 | 183 | #define ZEND_MODULE_GLOBALS_ACCESSOR(module_name, v) ZEND_TSRMG(module_name##_globals_id, zend_##module_name##_globals *, v) 184 | 185 | #else 186 | 187 | #define ZEND_MODULE_GLOBALS_ACCESSOR(module_name, v) (module_name##_globals.v) 188 | 189 | #endif 190 | 191 | 192 | #ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE 193 | #define ZEND_TSRMG TSRMG_STATIC 194 | #else 195 | #define ZEND_TSRMG TSRMG 196 | #endif 197 | 198 | ```` 199 | 使用它们会让你的方法看起来更简洁: 200 | 201 | ````c 202 | PHP_FUNCTION(sample4_counter) { 203 | RETURN_LONG(++SAMPLE4_G(counter)); 204 | } 205 | ```` 206 | 207 | 看到*G()这样的宏是不是有种似曾相识的感觉?也许以前你看到过EG()、CG()等宏,了解他们会让你对PHP的了解更深一步: 208 | 209 | Accessor Macro | Associated Data 210 | ------------- | ------------- 211 | EG() | 这个宏可以用来访问符号表,函数,资源信息和常量。 212 | CG() | 用来访问核心全局变量。 213 | PG() | PHP全局变量。我们知道php.ini会映射一个或者多个PHP全局结构。举几个使用这个宏的例子:PG(register_globals), PG(safe_mode), PG(memory_limit) 214 | FG() | 文件全局变量。大多数文件I/O或相关的全局变量的数据流都塞进标准扩展出口结构。 215 | 216 | ## links 217 | * [目录]() 218 | * 12.3 [常量](<12.3.md>) 219 | * 12.5 [PHP语言中的超级全局变量](<12.5.md>) 220 | 221 | -------------------------------------------------------------------------------- /12.5.md: -------------------------------------------------------------------------------- 1 | # 12.5 PHP语言中的超级全局变量(Superglobals) 2 | 3 | 在PHP中有一种“特殊”的全局变量,通常我们把它们称作超级全局变量,常见的比如`$_GET`、`$_POST`、`$_FILE`等等。 4 | 5 | 他们会在编译之前就声明,所以在普通的脚本中,可能无法定义其它的超级全局变量。在扩展中,最好的使用超级全局变量的是session扩展,它使用`$_SESSION`来 6 | 在`session_start()`和`session_write_close()`之间存储信息。那么是怎样定义`$_SESSION`这个超级全局变量的呢?我们来看下session扩展的`MINIT`函数实现: 7 | 8 | ````c 9 | PHP_MINIT_FUNCTION(session) { 10 | zend_register_auto_global("_SESSION", 11 | sizeof("_SESSION") - 1, 12 | NULL TSRMLS_CC); 13 | return SUCCESS; 14 | } 15 | ```` 16 | 17 | php7 18 | 19 | ````c 20 | PHP_MINIT_FUNCTION(session) { 21 | 22 | zend_class_entry ce; 23 | 24 | zend_register_auto_global(zend_string_init("_SESSION", sizeof("_SESSION") - 1, 1), 0, NULL); 25 | ... 26 | } 27 | 28 | ```` 29 | 30 | 注意这里的第二个参数,`sizeof("_SESSION") - 1`,一定要排除标识字符串结束的\0符。php7的第二个参数是bool型的jit标志。 31 | 32 | 我们一起来看下`zend_register_auto_global()`这个函数在ZE2中的原型: 33 | 34 | ````c 35 | int zend_register_auto_global(char *name, uint name_len, 36 | zend_auto_global_callback auto_global_callback TSRMLS_DC) 37 | ```` 38 | 39 | (php7) 40 | 41 | ```c 42 | ZEND_API int zend_register_auto_global(zend_string *name, zend_bool jit, zend_auto_global_callback auto_global_callback); 43 | ``` 44 | 45 | 在ZE1中,是没有`auto_global_callback`这个参数的。为了和PHP4兼容,我们仍需要像下面这样声明一个超级全局变量: 46 | 47 | ````c 48 | PHP_MINIT_FUNCTION(sample4) { 49 | zend_register_auto_global("_SAMPLE4", sizeof("_SAMPLE4") - 1 50 | #ifdef ZEND_ENGINE_2 51 | , NULL 52 | #endif 53 | TSRMLS_CC); 54 | return SUCCESS; 55 | } 56 | ```` 57 | 58 | ###全局变量的回调 59 | 60 | 在ZE2中,`zend_register_auto_global()`函数的`auto_global_callback`参数接受一个自定义函数。在实践中,这样的做法可以用来避免复杂的初始化,我们来 61 | 看下面这一段代码: 62 | 63 | ````c 64 | zend_bool php_sample4_autoglobal_callback(char *name, uint name_len TSRMLS_DC) 65 | { 66 | zval *sample4_val; 67 | int i; 68 | MAKE_STD_ZVAL(sample4_val);//php7中要注释掉这行 69 | array_init(sample4_val); 70 | for(i = 0; i < 10000; i++) { 71 | add_next_index_long(sample4_val, i); 72 | } 73 | ZEND_SET_SYMBOL(&EG(symbol_table), "_SAMPLE4", sample4_val); 74 | zend_set_local_var_str("http_response_header", sizeof("http_response_header")-1, &ztmp, 0); 75 | 76 | return 0; 77 | } 78 | 79 | 80 | zend_set_local_var_str("http_response_header", sizeof("http_response_header")-1, &ztmp, 0); 81 | PHP_MINIT_FUNCTION(sample4) { 82 | zend_register_auto_global("_SAMPLE4", sizeof("_SAMPLE4") - 1 83 | #ifdef ZEND_ENGINE_2 84 | , php_sample4_autoglobal_callback 85 | #endif 86 | TSRMLS_CC); 87 | return SUCCESS; 88 | } 89 | ```` 90 | 91 | php7 92 | 93 | ````c 94 | zend_bool php_sample4_autoglobal_callback(char *name, uint name_len TSRMLS_DC) 95 | { 96 | zval *sample4_val; 97 | int i; 98 | array_init(sample4_val); 99 | for(i = 0; i < 10000; i++) { 100 | add_next_index_long(sample4_val, i); 101 | } 102 | zend_set_local_var_str("_SAMPLE4", sizeof("_SAMPLE4")-1, ztmp, 0); 103 | 104 | return 0; 105 | } 106 | 107 | 108 | PHP_MINIT_FUNCTION(sample4) { 109 | zend_register_auto_global("_SAMPLE4", sizeof("_SAMPLE4") - 1 110 | #ifdef ZEND_ENGINE_2 111 | , php_sample4_autoglobal_callback 112 | #endif 113 | TSRMLS_CC); 114 | return SUCCESS; 115 | } 116 | ```` 117 | 118 | 不幸的是,这样的设计打破了PHP4和ZE1的规则,它们不支持这样的回调。所以,为了兼容它们,我们要在每个脚本开始的时候去调用我们的回调函数(RINIT): 119 | 120 | ````c 121 | PHP_RINIT_FUNCTION(sample4) { 122 | #ifdef ZEND_ENGINE_2 123 | php_sample4_autoglobal_callback("_SAMPLE4", 124 | sizeof("_SAMPLE4") - 1, 125 | TSRMLS_CC); 126 | #endif 127 | return SUCCESS; 128 | } 129 | ```` 130 | 131 | 132 | ## links 133 | * [目录]() 134 | * 12.4 [PHP扩展中的全局变量](<12.4.md>) 135 | * 12.6 [小结](<12.6.md>) 136 | 137 | -------------------------------------------------------------------------------- /12.6.md: -------------------------------------------------------------------------------- 1 | # 12.6 小结 2 | 3 | 通过本章的课程,我们深入了解了PHP的生命周期,常量、全局变量和超级全局变量的定义和使用。在下一章中,你会学会如何声明和使用的php.ini值。 4 | 5 | ## links 6 | * [目录]() 7 | * 12.5 [PHP语言中的超级全局变量](<12.5.md>) 8 | * 13 [设置INI](<13.md>) 9 | 10 | -------------------------------------------------------------------------------- /12.md: -------------------------------------------------------------------------------- 1 | # 12 启动与终止的那点事 2 | 3 | ##目录 4 | 5 | * [1. 关于生命周期](12.1.md) 6 | * [2. MINFO与phpinfo](<12.2.md>) 7 | * [3. 常量](<12.3.md>) 8 | * [4. PHP扩展中的全局变量](<12.4.md>) 9 | * [5. PHP语言中的超级全局变量](<12.5.md>) 10 | * [6. 小结](<12.6.md>) 11 | 12 | 13 | 在前面的章节里,你已经学会了如何使用MINIT函数在PHP加载模块的共享库时来执行初始化任务。在第一章,你还了解到扩展里其他三个函数, 14 | 和MINIT函数对应的MSHUTDOWN函数,以及一对在每个页面请求开始和结束时候调用的方法--RINIT函数和RSHUTDOWN函数。 15 | 16 | 17 | ## links 18 | * [目录]() 19 | * 11.3 [小结](<11.3.md>) 20 | * 12.1 [关于生命周期](<12.1.md>) 21 | 22 | -------------------------------------------------------------------------------- /13.2.md: -------------------------------------------------------------------------------- 1 | # 13.2 小结 2 | 3 | 在这章,你探索了PHP中最古老的特性,也可以说是PHP健壮可移植性的最大的问题所在。对于每个有用的INI设置,它对编程的障碍就是随时都会出现它,这让编程越来越复杂。INI设置是一把双刃剑,所以在使用之前一定要深思熟虑,不然造成的后果将是长久的;胡乱使用将使你的系统出现很多不可预测的问题。 4 | 5 | 在接下来的三章中,你将专研数据流API,从使用开始,进一步提升到数据流的实现层,包装、环境以及过滤器 6 | 7 | ## links 8 | * [目录]() 9 | * 13.1 [声明和访问INI设置](<13.1.md>) 10 | * 14 [访问数据流](<14.md>) -------------------------------------------------------------------------------- /13.md: -------------------------------------------------------------------------------- 1 | # 13 INI设置 2 | ##目录 3 | 4 | * [1. 声明和访问ini设置](13.1.md) 5 | * [2. 小结](<12.2.md>) 6 | 7 | 在前面的一章,我们已经学会了MINIT、MSHUTDOWN函数,以及RINIT和RSHUTDOWN等函数的使用,这里我们将介绍并学习ini设置的使用。 8 | 9 | 10 | ## links 11 | * [目录]() 12 | * 12.6 [小结](<12.6.md>) 13 | * 13.1 [声明和访问INI设置](<13.1.md>) -------------------------------------------------------------------------------- /14.3.md: -------------------------------------------------------------------------------- 1 | # 静态资源操作 2 | 3 | 一个基于流的原子操作并不需要实际的实例. 下面这些API仅仅使用URL执行这样的操作: 4 | 5 | ```c 6 | int php_stream_stat_path(char *path, php_stream_statbuf *ssb); 7 | ``` 8 | 9 | 和前面的php_stream_stat()类似, 这个函数提供了一个对POSIX的stat()函数协议依赖的包装. 要注意, 并不是所有的协议都支持URL记法, 并且即便支持也可能不能报告出statbuf结构体中的所有成员值. 一定要检查php_stream_stat_path()失败时的返回值, 0标识成功, 要知道, 不支持的元素返回时其值将是默认的0. 10 | 11 | ```c 12 | int php_stream_stat_path_ex(char *path, int flags, 13 | php_stream_statbuf *ssb, php_stream_context *context); 14 | ``` 15 | 16 | 这个php_stream_url_stat()的扩展版本允许传递另外两个参数. 第一个是flags, 它的值可以是下面的PHP_STERAM_URL_STAT_*(下面省略PHP_STREAM_URL_STAT_前缀)一族常量的按位或的结果. 还有一个是context参数, 它在其他的一些流函数中也有出现,我们将在第16章去详细学习. 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
LINK原始的php_stream_stat_path()对于符号链接或目录将会进行解析直到碰到协议定义的结束资源. 传递PHP_STREAM_URL_STAT_LINK标记将导致php_stream_stat_path()返回请求资源的信息而不会进行符号链接的解析.(译注: 我们可以这样理解, 没有这个标记, 底层使用stat(), 如果有这个标记,底层使用lstat(), 关于stat()和lstat()的区别, 请查看*nix手册)
QUIET默认情况下, 如果在执行URL的stat操作过程中碰到错误, 包括文件未找到错误, 都将通过php的错误处理机制触发. 传递QUIET标记可以使得php_stream_stat_path()返回而不报告错误.
28 | 29 | ```c 30 | int php_stream_mkdir(char *path, int mode, int options, 31 | php_stream_context *context); 32 | int php_stream_rmdir(char *path, int options, 33 | php_stream_context *context); 34 | ``` 35 | 36 | 创建和删除目录也会如你期望的工作. 这里的options参数和前面的php_stream_open_wrapper()函数的同名参数含义一致. 对于php_stream_mkdir(), 还有一个参数mode用于指定一个八进制的值表明读写执行权限. 37 | 38 | ## links 39 | * [目录]() 40 | * 14.2 [访问流](<14.2.md>) 41 | * 14.4 [小结](<14.4.md>) 42 | -------------------------------------------------------------------------------- /14.4.md: -------------------------------------------------------------------------------- 1 | #小结 2 | 3 | 本章中你接触了一些基于流的I/O的内部表象. 下一章将演示做呢样实现自己的协议包装, 甚至是定义自己的流类型. 4 | 5 | ## links 6 | * [目录]() 7 | * 14.3 [访问流](<14.3.md>) 8 | * 15 [实现流](<15.md>) 9 | -------------------------------------------------------------------------------- /14.md: -------------------------------------------------------------------------------- 1 | # 14 流式访问 2 | 3 | ##目录 4 | 5 | * [1. 概览](14.1.md) 6 | * [2. 打开流](14.2.md) 7 | * [3. 访问流](14.3.md) 8 | * [4. 静态资源操作](14.4md) 9 | * [5. 小结](<14.5.md>) 10 | 11 | PHP用户空间中所有的文件I/O处理都是通过php 4.3引入的php流包装层处理的。在内部,扩展代码可以选择使用stdio或posix文件处理和本地文件系统或伯克利域套接字进行通信,或者也可以调用和用户空间流I/O相同的API。 12 | 13 | 14 | ## links 15 | * [目录]() 16 | * 12.6 [小结](<13.2.md>) 17 | * 14.1 [概览](<14.1.md>) 18 | -------------------------------------------------------------------------------- /15.1.md: -------------------------------------------------------------------------------- 1 | # 15.1 php流的表象之下 2 | 3 | 对于给定的流实例, 比如文件流和网络流, 它们的不同在于上一章你使用的流创建函数返回的php_stream结构体中的ops成员(人话就是返回的php_stream中的ops不同). 4 | ```c 5 | typedef struct _php_stream { 6 | ... 7 | php_stream_ops *ops; 8 | ... 9 | } php_stream; 10 | ``` 11 | php_stream_ops结构体定义的是一个函数指针集合以及一个描述标记. 12 | ```c 13 | typedef struct _php_stream_ops { 14 | size_t (*write)(php_stream *stream, const char *buf, 15 | size_t count TSRMLS_DC); 16 | size_t (*read)(php_stream *stream, char *buf, 17 | size_t count TSRMLS_DC); 18 | int (*close)(php_stream *stream, int close_handle 19 | TSRMLS_DC); 20 | int (*flush)(php_stream *stream TSRMLS_DC); 21 | 22 | const char *label; 23 | 24 | int (*seek)(php_stream *stream, off_t offset, int whence, 25 | off_t *newoffset TSRMLS_DC); 26 | int (*cast)(php_stream *stream, int castas, void **ret 27 | TSRMLS_DC); 28 | int (*stat)(php_stream *stream, php_stream_statbuf *ssb 29 | TSRMLS_DC); 30 | int (*set_option)(php_stream *stream, int option,int value, 31 | void *ptrparam TSRMLS_DC); 32 | } php_stream_ops; 33 | ``` 34 | 35 | (php7) 36 | 37 | ````c 38 | 39 | typedef struct _php_stream_ops { 40 | 41 | /* stdio like functions - these are mandatory! */ 42 | size_t (*write)(php_stream *stream, const char *buf, size_t count); 43 | size_t (*read)(php_stream *stream, char *buf, size_t count); 44 | int (*close)(php_stream *stream, int close_handle); 45 | int (*flush)(php_stream *stream); 46 | 47 | const char *label; /* label for this ops structure */ 48 | 49 | /* these are optional */ 50 | int (*seek)(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset); 51 | int (*cast)(php_stream *stream, int castas, void **ret); 52 | int (*stat)(php_stream *stream, php_stream_statbuf *ssb); 53 | int (*set_option)(php_stream *stream, int option, int value, void *ptrparam); 54 | } php_stream_ops; 55 | 56 | 57 | ```` 58 | 当流访问函数比如php_stream_read()被调用时, 流包装层实际上解析调用了stream->ops中对应的函数, 这样实际调用的就是当前流类型特有的read实现. 比如, 普通文件的流ops结构体中的read函数实现如下(实际的该实现比下面的示例复杂一点): 59 | ```c 60 | size_t php_stdiop_read(php_stream *stream, char *buf, 61 | size_t count TSRMLS_DC) 62 | { 63 | php_stdio_stream_data *data = 64 | (php_stdio_stream_data*)stream->abstract; 65 | return read(data->fd, buf, count); 66 | } 67 | ``` 68 | 而compress.zlib流使用的ops结构体中则read则指向的是如下的函数: 69 | ```c 70 | size_t php_zlib_read(php_stream *stream, char *buf, 71 | size_t count TSRMLS_DC) 72 | { 73 | struct php_gz_stream_data_t *data = 74 | (struct php_gz_stream_data_t *) stream->abstract; 75 | 76 | return gzread(data->gz_file, buf, count); 77 | } 78 | 79 | ``` 80 | 81 | 这里第一点需要注意的是ops结构体指向的函数指针常常是对数据源真正的读取函数的一个瘦代理. 在上面两个例子中, 标准I/O流使用posix的read()函数, 而zlib流使用的是libz的gzread()函数. 82 | 83 | 你可能还注意到了, 这里使用了stream->abstract元素. 这是流实现的一个便利指针, 它可以被用于获取各种相关的捆绑信息. 在上面的例子中, 指向自定义结构体的指针, 用于存储底层read函数要使用的文件描述符. 84 | 85 | 还有一件你可能注意到的事情是php_stream_ops结构体中的每个函数都期望一个已有的流实例, 但是怎样得到实例呢? abstract成员是怎样设置的以及什么时候流指示使用哪个ops结构体? 答案就在你在上一章使用过的第一个打开流的函数(php_stream_open_wrapper())中. 86 | 87 | 当这个函数被调用时, php的流包装层尝试基于传递的URL中的scheme://部分确定请求的是什么协议. 这样它就可以在已注册的php包装器中查找对应的php_stream_wrapper项. 每个php_stream_wrapper结构体都可以取到自己的ops元素, 它指向一个php_stream_wrapper_ops结构体: 88 | ```c 89 | typedef struct _php_stream_wrapper_ops { 90 | 91 | php_stream *(*stream_opener)(php_stream_wrapper *wrapper, 92 | char *filename, char *mode, 93 | int options, char **opened_path, 94 | php_stream_context *context 95 | STREAMS_DC TSRMLS_DC); 96 | int (*stream_closer)(php_stream_wrapper *wrapper, 97 | php_stream *stream TSRMLS_DC); 98 | int (*stream_stat)(php_stream_wrapper *wrapper, 99 | php_stream *stream, 100 | php_stream_statbuf *ssb 101 | TSRMLS_DC); 102 | int (*url_stat)(php_stream_wrapper *wrapper, 103 | char *url, int flags, 104 | php_stream_statbuf *ssb, 105 | php_stream_context *context 106 | TSRMLS_DC); 107 | php_stream *(*dir_opener)(php_stream_wrapper *wrapper, 108 | char *filename, char *mode, 109 | int options, char **opened_path, 110 | php_stream_context *context 111 | STREAMS_DC TSRMLS_DC); 112 | 113 | const char *label; 114 | 115 | int (*unlink)(php_stream_wrapper *wrapper, char *url, 116 | int options, 117 | php_stream_context *context 118 | TSRMLS_DC); 119 | 120 | int (*rename)(php_stream_wrapper *wrapper, 121 | char *url_from, char *url_to, 122 | int options, 123 | php_stream_context *context 124 | TSRMLS_DC); 125 | 126 | 127 | int (*stream_mkdir)(php_stream_wrapper *wrapper, 128 | char *url, int mode, int options, 129 | php_stream_context *context 130 | TSRMLS_DC); 131 | int (*stream_rmdir)(php_stream_wrapper *wrapper, char *url, 132 | int options, 133 | php_stream_context *context 134 | TSRMLS_DC); 135 | } php_stream_wrapper_ops; 136 | ``` 137 | 138 | (php7) 139 | 140 | ````c 141 | 142 | typedef struct _php_stream_wrapper_ops { 143 | /* open/create a wrapped stream */ 144 | php_stream *(*stream_opener)(php_stream_wrapper *wrapper, const char *filename, const char *mode, 145 | int options, zend_string **opened_path, php_stream_context *context STREAMS_DC); 146 | /* close/destroy a wrapped stream */ 147 | int (*stream_closer)(php_stream_wrapper *wrapper, php_stream *stream); 148 | /* stat a wrapped stream */ 149 | int (*stream_stat)(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb); 150 | /* stat a URL */ 151 | int (*url_stat)(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context); 152 | /* open a "directory" stream */ 153 | php_stream *(*dir_opener)(php_stream_wrapper *wrapper, const char *filename, const char *mode, 154 | int options, zend_string **opened_path, php_stream_context *context STREAMS_DC); 155 | 156 | const char *label; 157 | 158 | /* delete a file */ 159 | int (*unlink)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context); 160 | 161 | /* rename a file */ 162 | int (*rename)(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context); 163 | 164 | /* Create/Remove directory */ 165 | int (*stream_mkdir)(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context); 166 | int (*stream_rmdir)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context); 167 | /* Metadata handling */ 168 | int (*stream_metadata)(php_stream_wrapper *wrapper, const char *url, int options, void *value, php_stream_context *context); 169 | } php_stream_wrapper_ops; 170 | 171 | ```` 172 | 这里, 流包装层调用wrapper->ops->stream_opener(), 它将执行包装器特有的操作创建流实例, 赋值恰当的php_stream_ops结构体, 绑定相关的抽象数据. 173 | 174 | dir_opener()函数和stream_opener()提供相同的基础服务; 不过, 它是对php_stream_opendir()这个API调用的响应, 并且通常会绑定一个不同的php_stream_ops结构体到返回的实例. stat()和close()函数在这一层上是重复的, 这样做是为了给包装器的这些操作增加协议特有的逻辑. 175 | 176 | 其他的函数则允许执行静态流操作而不用实际的创建流实例. 回顾这些流API调用, 它们并不实际返回php_stream对象, 你马上就会看到它们的细节. 177 | 178 | > 尽管在php 4.3中引入流包装层时, url_stat在内部作为一个包装器的ops函数存在, 但直到php 5.0它才开始被使用. 此外, 最后的3个函数, rename(), stream_mkdir()以及stream_rmdir()一直到php 5.0才引入, 在这个版本之前, 它们并不在包装器的ops结构中. 179 | 180 | 181 | ## links 182 | * [目录]() 183 | * 15 [流的实现](<15.md>) 184 | * 15.2 [包装器操作](<15.2.md>) 185 | -------------------------------------------------------------------------------- /15.2.md: -------------------------------------------------------------------------------- 1 | # 包装器操作 2 | 3 | 除了url_stat()函数, 包装器操作中在const char *label元素之前的每个操作都可以用于激活的流实例上. 每个函数的意义如下: 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 28 | 29 |
stream_opener() 8 | 实例化一个流实例. 当某个用户空间的fopen()函数被调用时, 这个函数将被调用. 这个函数返回的php_stream实例是fopen()函数返回的文件资源句柄的内部表示. 集成函数比如file(), file_get_contents(), file_put_contents(), readfile()等等, 在请求包装资源时, 都使用这个包装器ops. 9 |
stream_closer() 14 | 当一个流实例结束其生命周期时这个函数被调用. stream_opener()时分配的所有资源都应该在这个函数中被释放. 15 |
stream_stat() 20 | 类似于用户空间的fstat()函数, 这个函数应该填充ssb结构体(实际上只包含一个struct statbuf sb结构体成员), 21 |
dir_opener() 26 | 和stream_opener()行为一致, 不过它是调用opendir()一族的用户空间函数时被调用的. 目录流使用的底层流实现和文件流遵循相同的规则;不过目录流只需要返回包含在打开的目录中找到的文件名的记录, 它的大小为struct dirent这个结构体的大小. 27 |
30 | 31 | ## 静态包装器操作 32 | 33 | 包装器操作函数中的其他函数是在URI路径上执行原子操作, 具体取决于包装器协议. 在php4.3的php_stream_wrapper_ops结构体中只有url_stat()和unlink(); 其他的方式是到php 5.0后才定义的, 编码时应该适时的使用#ifdef块说明. 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 58 | 59 |
url_stat() 38 | stat()族函数使用, 返回文件元数据, 比如访问授权, 大小, 类型; 以及访问, 修改,创建时间. 尽管这个函数是在php 4.3引入流包装层时出现在php_stream_wrapper_ops结构体中的, 但直到php 5.0才被用户空间的stat()函数使用. 39 |
unlink() 44 | 和posix文件系统的同名函数语义相同, 它执行文件删除. 如果对于当前的包装器删除没有意义, 比如内建的http://包装器, 这个函数应该被定义为NULL, 以便内核去引发适当的错误消息. 45 |
rename() 50 | 当用户空间的rename()函数的参数$from和$to参数指向的是相同的底层包装器实现, php则将这个重命名请求分发到包装器的rename函数. 51 |
mkdir() & rmdir() 56 | 这两个函数直接映射到对应的用户空间函数. 57 |
60 | 61 | 62 | 63 | ## links 64 | * [目录]() 65 | * 15.1 [php流的表象之下](<15.1.md>) 66 | * 15.3 [实现一个包装器](<15.3.md>) 67 | -------------------------------------------------------------------------------- /15.4.md: -------------------------------------------------------------------------------- 1 | # 操纵 2 | TBD 3 | 4 | 5个静态包装器操作中的4个用来处理不是基于I/O的流资源操作. 你已经看到过它们并了解它们的原型; 现在我们看看varstream包装器框架中它们的实现: 5 | 6 | ## unlink 7 | 8 | 在你的wrapper_ops结构体中增加下面的函数, 它可以让unlink()通过varstream包装器, 拥有和unset()一样的行为: 9 | ```c 10 | static int php_varstream_unlink(php_stream_wrapper *wrapper, 11 | char *filename, int options, 12 | php_stream_context *context 13 | TSRMLS_DC) 14 | { 15 | php_url *url; 16 | 17 | url = php_url_parse(filename); 18 | if (!url) { 19 | php_stream_wrapper_log_error(wrapper, options 20 | TSRMLS_CC, "Unexpected error parsing URL"); 21 | return -1; 22 | } 23 | if (!url->host || (url->host[0] == 0) || 24 | strcasecmp("var", url->scheme) != 0) { 25 | /* URL不合法 */ 26 | php_stream_wrapper_log_error(wrapper, options 27 | TSRMLS_CC, "Invalid URL, must be in the form: " 28 | "var://variablename"); 29 | php_url_free(url); 30 | return -1; 31 | } 32 | 33 | /* 从符号表删除变量 */ 34 | //zend_hash_del(&EG(symbol_table), url->host, strlen(url->host) + 1); 35 | zend_delete_global_variable(url->host, strlen(url->host) + 1 TSRMLS_CC); 36 | 37 | php_url_free(url); 38 | return 0; 39 | } 40 | ``` 41 | 这个函数的编码量和php_varstream_opener差不多. 唯一的不同在于这里你需要传递变量名给zend_hash_del()去删除变量. 42 | 43 | >译注: 译者的php-5.4.10环境中, 使用unlink()删除变量后, 在用户空间再次读取该变量名的值会导致core dump. 因此上面代码中译者进行了修正, 删除变量时使用了zend_delete_global_variable(), 请读者参考阅读zend_delete_global_variable()函数源代码, 考虑为什么直接用zend_hash_del()删除, 会导致core dump. 下面是译者测试用的用户空间代码: 44 | 45 | ```php 46 | host, 76 | strlen(from->host) + 1, 77 | (void**)&var) == FAILURE) { 78 | php_stream_wrapper_log_error(wrapper, options 79 | TSRMLS_CC, "$%s does not exist", from->host); 80 | php_url_free(from); 81 | return -1; 82 | } 83 | /* 目标URL解析 */ 84 | to = php_url_parse(url_to); 85 | if (!to) { 86 | php_stream_wrapper_log_error(wrapper, options 87 | TSRMLS_CC, "Unexpected error parsing dest"); 88 | php_url_free(from); 89 | return -1; 90 | } 91 | /* 变量的改名 */ 92 | Z_SET_REFCOUNT_PP(var, Z_REFCOUNT_PP(var) + 1); 93 | zend_hash_update(&EG(symbol_table), to->host, 94 | strlen(to->host) + 1, (void*)var, 95 | sizeof(zval*), NULL); 96 | zend_hash_del(&EG(symbol_table), from->host, 97 | strlen(from->host) + 1); 98 | php_url_free(from); 99 | php_url_free(to); 100 | return 0; 101 | } 102 | 103 | static int php_varstream_mkdir(php_stream_wrapper *wrapper, 104 | char *url_from, int mode, int options, 105 | php_stream_context *context TSRMLS_DC) 106 | { 107 | php_url *url; 108 | 109 | /* URL解析 */ 110 | url = php_url_parse(url_from); 111 | if (!url) { 112 | php_stream_wrapper_log_error(wrapper, options 113 | TSRMLS_CC, "Unexpected error parsing URL"); 114 | return -1; 115 | } 116 | 117 | /* 检查变量是否存在 */ 118 | if (zend_hash_exists(&EG(symbol_table), url->host, 119 | strlen(url->host) + 1)) { 120 | php_stream_wrapper_log_error(wrapper, options 121 | TSRMLS_CC, "$%s already exists", url->host); 122 | php_url_free(url); 123 | return -1; 124 | } 125 | /* EG(uninitialized_zval_ptr)通常是IS_NULL的zval *, 引用计数无限大 */ 126 | zend_hash_add(&EG(symbol_table), url->host, 127 | strlen(url->host) + 1, 128 | (void*)&EG(uninitialized_zval_ptr), 129 | sizeof(zval*), NULL); 130 | php_url_free(url); 131 | return 0; 132 | } 133 | 134 | static int php_varstream_rmdir(php_stream_wrapper *wrapper, 135 | char *url, int options, 136 | php_stream_context *context TSRMLS_DC) 137 | { 138 | /* 行为等价于unlink() */ 139 | wrapper->wops->unlink(wrapper, url, options, 140 | context TSRMLS_CC); 141 | } 142 | ``` 143 | 144 | 145 | 146 | ## links 147 | * [目录]() 148 | * 15.3 [实现一个包装器](<15.3.md>) 149 | * 15.5 [检查](<15.5.md>) 150 | -------------------------------------------------------------------------------- /15.5.md: -------------------------------------------------------------------------------- 1 | # 检查 2 | 3 | 并不是所有的流操作都涉及到资源的操纵. 有时候也需要查看活动的流在某个时刻的状态, 或检查潜在可打开的资源的状态. 4 | 5 | 这一节流和包装器的ops函数都是在相同的数据结构php_stream_statbuf上工作的, 它只有一个元素: posix标准的struct statbuf. 当本节的某个函数被调用时, 将尝试填充尽可能多的statbuf元素的成员. 6 | 7 | ## stat 8 | 9 | 如果设置, 当请求激活流实例的信息时, 将会调用wrapper->ops->stream_stat(). 如果没有设置, 则对应的stream->ops->stat()将会被调用. 无论哪个函数被调用, 都应该尽可能多的向返回的statbuf结构体ssb->sb中填充尽可能多流实例的有用信息. 在普通文件I/O的用法中, 它对应fstat()的标准I/O调用. 10 | 11 | ## url_stat 12 | 13 | 在流实例外部调用wrapper->ops->url_stat()取到流资源的元数据. 通常来说, 符号链接和重定向都应该被解析, 直到找到一个真正的资源, 对其通过stat()系统调用这样的机制读取统计信息. url_stat的flags参数允许是下面PHP_STREAM_URL_STAT_*系列的常量值(省略PHP_STREAM_URL_STAT_前缀): 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 26 | 27 |
LINK 18 | 不解析符号链接和重定向. 而是报告它碰到的第一个节点的信息, 无论是连接还是真正的资源. 19 |
QUIET 24 | 不报告错误. 注意, 这和许多其他流函数中的REPORT_ERRORS逻辑恰恰相反. 25 |
28 | 29 | 30 | ## links 31 | * [目录]() 32 | * 15.4 [操纵](<15.4.md>) 33 | * 15.6 [小结](<15.6.md>) 34 | -------------------------------------------------------------------------------- /15.6.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 无论是暴露远程网络I/O还是本地数据源的流资源, 都允许你的扩展在核心数据上挂在操纵函数的钩子, 避免重新实现单调的描述符管理和I/O缓冲区工作. 这使得它在用户空间环境中更加有用, 更加强大. 4 | 5 | 下一章将通过对过滤器和上下文的学习结束流包装层的学习, 过滤器和上下文可以用于选择默认的流行为, 甚至过程中修改数据. 6 | 7 | 8 | ## links 9 | * [目录]() 10 | * 15.5 [检查](<15.5.md>) 11 | * 16 [有趣的流](<16.md>) 12 | -------------------------------------------------------------------------------- /15.md: -------------------------------------------------------------------------------- 1 | # 15 流的实现 2 | 3 | ##目录 4 | 5 | * [1. php流的表象之下](15.1.md) 6 | * [2. 包装器操作](15.2.md) 7 | * [3. 实现一个包装器](15.3.md) 8 | * [4. 操纵](15.4md) 9 | * [5. 检查](<15.5.md>) 10 | * [6. 小结](<15.6.md>) 11 | 12 | php的流最强力的特性之一是它可以访问众多数据源: 普通文件, 压缩文件, 网络透明 通道, 加密网络, 命名管道以及域套接字, 它们对于用户空间以及内部都是统⼀的API. 13 | 14 | 15 | ## links 16 | * [目录]() 17 | * 14.5 [小结](<14.5.md>) 18 | * 15.1 [php流的表象之下](<15.1.md>) 19 | -------------------------------------------------------------------------------- /16.1.md: -------------------------------------------------------------------------------- 1 | # 上下文 2 | 3 | 每个流的上下文包含两种内部消息类型. 首先最常用的是上下文选项. 这些值被安排在上下文中一个二维数组中, 通常用于改变流包装器的初始化行为. 还有一种则是上下文参数, 它对于包装器是未知的, 当前提供了一种方式用于在流包装层内部的事件通知. 4 | ```c 5 | php_stream_context *php_stream_context_alloc(void); 6 | ``` 7 | 通过这个API调用可以创建一个上下文, 它将分配一些存储空间并初始化用于保存上下文选项和参数的HashTable. 还会自动的注册为一个请求终止后将被清理的资源. 8 | 9 | ## 设置选项 10 | 11 | 设置上下文选项的内部API和用户空间的API是等同的: 12 | ```c 13 | int php_stream_context_set_option(php_stream_context *context, 14 | const char *wrappername, const char *optionname, 15 | zval *optionvalue); 16 | ``` 17 | 18 | 下面是用户空间的原型: 19 | ```c 20 | bool stream_context_set_option(resource $context, 21 | string $wrapper, string $optionname, 22 | mixed $value); 23 | ``` 24 | 25 | 它们的不同仅仅是用户空间和内部需要的数据类型不同.下面的例子就是使用这两个API调用, 通过内建包装器发起一个HTTP请求, 并通过一个上下文选项覆写了user_agent设置. 26 | ```c 27 | php_stream *php_varstream_get_homepage(const char *alt_user_agent TSRMLS_DC) 28 | { 29 | php_stream_context *context; 30 | zval tmpval; 31 | 32 | context = php_stream_context_alloc(TSRMLS_C); 33 | ZVAL_STRING(&tmpval, alt_user_agent, 0); 34 | php_stream_context_set_option(context, "http", "user_agent", &tmpval); 35 | return php_stream_open_wrapper_ex("http://www.php.net", "rb", REPORT_ERRORS | ENFORCE_SAFE_MODE, NULL, context); 36 | } 37 | ``` 38 | 39 | >译者使用的php-5.4.10中php_stream_context_alloc()增加了线程安全控制, 因此相应的对例子进行了修改, 请读者测试时注意. 40 | >这里要注意的是tmpval并没有分配任何持久性的存储空间, 它的字符串值是通过复制设置的. php_stream_context_set_option()会自动的对传入的zval内容进行一次拷贝. 41 | 42 | # 取回选项 43 | 44 | 用于取回上下文选项的API调用正好是对应的设置API的镜像: 45 | ```c 46 | int php_stream_context_get_option(php_stream_context *context, 47 | const char *wrappername, const char *optionname, 48 | zval ***optionvalue); 49 | ``` 50 | 51 | 回顾前面, 上下文选项存储在一个嵌套的HashTable中, 当从一个HashTable中取回值时, 一般的方法是传递一个指向zval **的指针给zend_hash_find(). 当然, 由于php_stream_context_get_option()是zend_hash_find()的一个特殊代理, 它们的语义是相同的. 52 | 53 | 下面是内建的http包装器使用php_stream_context_get_option()设置user_agent的简化版示例: 54 | ```c 55 | zval **ua_zval; 56 | char *user_agent = "PHP/5.1.0"; 57 | if (context && 58 | php_stream_context_get_option(context, "http", 59 | "user_agent", &ua_zval) == SUCCESS && 60 | Z_TYPE_PP(ua_zval) == IS_STRING) { 61 | user_agent = Z_STRVAL_PP(ua_zval); 62 | } 63 | ``` 64 | 这种情况下, 非字符串值将会被丢弃, 因为对用户代理字符串而言, 数值是没有意义的. 其他的上下文选项, 比如max_redirects, 则需要数字值, 由于在字符串的zval中存储数字值并不通用, 所以需要执行一个类型转换以使设置合法. 65 | 66 | 不幸的是这些变量是上下文拥有的, 因此它们不能直接转换; 而需要首先进行隔离再进行转换, 最终如果需要还要进行销毁: 67 | ```c 68 | long max_redirects = 20; 69 | zval **tmpzval; 70 | if (context && 71 | php_stream_context_get_option(context, "http", 72 | "max_redirects", &tmpzval) == SUCCESS) { 73 | if (Z_TYPE_PP(tmpzval) == IS_LONG) { 74 | max_redirects = Z_LVAL_PP(tmpzval); 75 | } else { 76 | zval copyval = **tmpzval; 77 | zval_copy_ctor(©val); 78 | convert_to_long(©val); 79 | max_redirects = Z_LVAL(copyval); 80 | zval_dtor(©val); 81 | } 82 | } 83 | ``` 84 | 85 | >实际上, 在这个例子中, zval_dtor()并不是必须的. IS_LONG的变量并不需要zval容器之外的存储空间, 因此zval_dtor()实际上不会有真正的操作. 在这个例子中包含它是为了完整性考虑, 对于字符串, 数组, 对象, 资源以及未来可能的其他类型, 就需要这个调用了. 86 | 87 | ## 参数 88 | 89 | 虽然用户空间API中看起来参数和上下文选项是类似的, 但实际上在语言内部的php_stream_context结构体中它们被定义为不同的成员. 90 | 91 | 目前只支持一个上下文参数: 通知器. php_stream_context结构体中的这个元素可以指向下面的php_stream_notifier结构体: 92 | ```c 93 | typedef struct { 94 | php_stream_notification_func func; 95 | void (*dtor)(php_stream_notifier *notifier); 96 | void *ptr; 97 | int mask; 98 | size_t progress, progress_max; 99 | } php_stream_notifier; 100 | ``` 101 | 102 | 当将一个php_stream_notifier结构体赋值给context->notifier时, 它将提供一个回调函数func, 在特定的流上发生下表中的PHP_STREAM_NOTIFY_*代码表示的事件时被触发. 每个事件将会对应下面第二张表中的PHP_STREAM_NOTIFY_SEVERITY_*的级别: 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
事件代码含义
RESOLVE主机地址解析完成. 多数基于套接字的包装器将在连接之前执行这个查询.
CONNECT套接字流连接到远程资源完成.
AUTH_REQUIRED请求的资源不可用, 原因是访问控制以及缺失授权
MIME_TYPE_IS远程资源的mime-type不可用
FILE_SIZE_IS远程资源当前可用大小
REDIRECTED原来的URL请求导致重定向到其他位置
PROGRESS由于额外数据的传输导致php_stream_notifier结构体的progress以及(可能的)progress_max元素被更新(进度信息, 请参考php手册curl_setopt的CURLOPT_PROGRESSFUNCTION和CURLOPT_NOPROGRESS选项)
COMPLETED流上没有更多的可用数据
FAILURE请求的URL资源不成功或未完成
AUTH_RESULT远程系统已经处理了授权认证
117 | 118 | 119 | 120 | 121 | 122 |
安全码 
INFO信息更新. 等价于一个E_NOTICE错误
WARN小的错误条件. 等价于一个E_WARNING错误
ERR中断错误条件. 等价于一个E_ERROR错误.
123 | 通知器实现提供了一个便利指针*ptr用于存放额外数据. 这个指针指向的空间必须在上下文析构时被释放, 因此必须指定一个dtor函数, 在上下文的最后一个引用离开它的作用域时调用这个dtor进行释放. 124 | 125 | mask元素允许事件触发限定特定的安全级别. 如果发生的事件没有包含在mask中, 则通知器函数不会被触发. 126 | 127 | 最后两个元素progress和progress_max可以由流实现设置, 然而, 通知器函数应该避免使用这两个值, 除非它接收到PHP_STREAM_NOTIFY_PROGRESS或PHP_STREAM_NOTIFY_FILE_SIZE_IS事件通知. 128 | 129 | 下面是一个php_stream_notification_func()回调原型的示例: 130 | ```c 131 | void php_sample6_notifier(php_stream_context *context, 132 | int notifycode, int severity, char *xmsg, int xcode, 133 | size_t bytes_sofar, size_t bytes_max, 134 | void *ptr TSRMLS_DC) 135 | { 136 | if (notifycode != PHP_STREAM_NOTIFY_FAILURE) { 137 | /* 忽略所有通知 */ 138 | return; 139 | } 140 | if (severity == PHP_STREAM_NOTIFY_SEVERITY_ERR) { 141 | /* 分发到错误处理函数 */ 142 | php_sample6_theskyisfalling(context, xcode, xmsg); 143 | return; 144 | } else if (severity == PHP_STREAM_NOTIFY_SEVERITY_WARN) { 145 | /* 日志记录潜在问题 */ 146 | php_sample6_logstrangeevent(context, xcode, xmsg); 147 | return; 148 | } 149 | } 150 | ``` 151 | 152 | ## 默认上下文 153 | 154 | 在php5.0中, 当用户空间的流创建函数被调用时, 如果没有传递上下文参数, 请求一般会使用默认的上下文. 这个上下文变量存储在文件全局结构中: FG(default_context), 并且它可以和其他所有的php_stream_context变量一样访问. 当在用户空间脚本执行流的创建时, 更好的方式是允许用户指定一个上下文或者至少指定一个默认的上下文. 将用户空间的zval *解码得到php_stream_context可以使用php_steram_context_from_zval()宏完成, 比如下面改编自第14章"访问流"的例子: 155 | ```c 156 | PHP_FUNCTION(sample6_fopen) 157 | { 158 | php_stream *stream; 159 | char *path, *mode; 160 | int path_len, mode_len; 161 | int options = ENFORCE_SAFE_MODE | REPORT_ERRORS; 162 | zend_bool use_include_path = 0; 163 | zval *zcontext = NULL; 164 | php_stream_context *context; 165 | 166 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 167 | "ss|br", &path, &path_len, &mode, &mode_len, 168 | &use_include_path, &zcontext) == FAILURE) { 169 | return; 170 | } 171 | context = php_stream_context_from_zval(zcontext, 0); 172 | if (use_include_path) { 173 | options |= PHP_FILE_USE_INCLUDE_PATH; 174 | } 175 | stream = php_stream_open_wrapper_ex(path, mode, options, 176 | NULL, context); 177 | if (!stream) { 178 | RETURN_FALSE; 179 | } 180 | php_stream_to_zval(stream, return_value); 181 | } 182 | ``` 183 | 184 | 如果zcontext包含一个用户空间的上下文资源, 通过ZEND_FETCH_RESOURCE()调用获取到它关联的指针设置到context中. 否则, 如果zcontext为NULL并且php_stream_context_from_zval()的第二个参数设置为非0值, 这个宏则直接返回NULL. 这个例子以及几乎所有的核心流创建的用户空间函数中, 第二个参数都被设置为0, 此时将使用FG(default_context)的值. 185 | 186 | ## links 187 | * [目录]() 188 | * 16 [有趣的流](<16.md>) 189 | * 16.2 [过滤器](<16.2.md>) 190 | -------------------------------------------------------------------------------- /16.3.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 过滤器和上下文可以让普通的流类型行为被修改, 或通过INI设置影响整个请求, 而不需要直接的代码修改. 使用本章设计的计数, 你可以使你自己的包装器实现更加强大, 并且可以对其他包装器产生的数据进行改变. 4 | 5 | 接下来, 我们将离开PHPAPI背后的工作, 回到php构建系统的机制, 产生更加复杂的扩展链接到其他应用, 找到更加容易的方法, 使用工具集处理重复的工作. 6 | 7 | 8 | ## links 9 | * [目录]() 10 | * 16.2 [过滤器](<16.2.md>) 11 | * 17 [配置和链接](<17.md>) 12 | -------------------------------------------------------------------------------- /16.md: -------------------------------------------------------------------------------- 1 | # 16 有趣的流 2 | 3 | ##目录 4 | 5 | * [1. 上下文](16.1.md) 6 | * [2. 过滤器](16.2.md) 7 | * [3. 小结](<16.3.md>) 8 | 9 | php常被提起的一个特性是流上下文. 这个可选的参数甚至在用户空间大多数流创建相关的函数中都可用, 它作为一个泛化的框架用于向给定包装器或流实现传入/传出额外的信息. 10 | 11 | 12 | ## links 13 | * [目录]() 14 | * 15.6 [小结](<15.6.md>) 15 | * 16.1 [上下文](<16.1.md>) 16 | -------------------------------------------------------------------------------- /17.1.md: -------------------------------------------------------------------------------- 1 | # autoconf 2 | 3 | 在一个简单的应用中, 你可能已经在你的Makefile中增加了下面这样的CFLAGS和LDFLAGS. 4 | 5 | ```c 6 | CFLAGS = ${CFLAGS} -I/usr/local/foobar/include 7 | LDFLAGS = ${LDFLAGS} -lfoobar -L/usr/local/foobar/lib 8 | ``` 9 | 10 | 想要构建你的应用却没有libfoobar的人, 或将libfoobar安装到其他位置的人, 将会得到一个处理过的错误消息, 用于帮助他找到错误原因. 11 | 12 | 在过去十年开发的多数开放源代码软件(OSS)以及PHP都利用了一个实用工具autoconf, 通过一些简单的宏来生成复杂的configure脚本. 这个产生的脚本会执行查找依赖库已经头文件是否安装的工作. 基于这些信息, 一个包可以自定义构建代码行, 或在编译的时间被浪费之前提供一个有意义的错误消息. 13 | 14 | 在构建php扩展时, 无论你是否计划公开发布, 都需要利用这个autoconf机制. 即便你对autoconf已经很熟悉了, 也请花几分钟时间阅读本章, php中引入了一些一般安装的autoconf没有的自定义宏. 15 | 16 | 和传统的autoconf步骤(集中的configure.in文件包含了包的所有配置宏)不同, php只是用configure.in管理许多位于源码树下小的config.m4脚本的协调, 包括各个扩展, SAPI, 核心自身, 以及ZendEngine. 17 | 18 | 你已经在前面的章节看到了一个简单版本的config.m4. 接下来, 我们将在这个文件中增加其他的autoconf语法, 让你的扩展可以收集到更多的配置时信息. 19 | 20 | 21 | ## links 22 | * [目录]() 23 | * 17 [配置和链接](<17.md>) 24 | * 17.2 [库的查找](<17.2.md>) 25 | -------------------------------------------------------------------------------- /17.2.md: -------------------------------------------------------------------------------- 1 | # 库的查找 2 | 3 | config.m4脚本最多是用于检查依赖库是否已安装. 扩展比如mysql, ldap, gmp以及其他设计为php用户空间和c库实现的其他功能之间的粘合层的扩展. 如果它们的依赖库没有安装, 或者安装的版本太旧, 要么会编译错误, 要么会导致产生的二进制无法运行. 4 | 5 | ## 头文件扫描 6 | 7 | 对依赖库扫描中最简单的一步就是检查你的脚本中的包含文件, 它们将在链接时使用. 下面的代码尝试在一些常见位置查找zlib.h: 8 | ```c 9 | PHP_ARG_WITH(zlib,[for zlib Support] 10 | [ with-zlib Include ZLIB Support]) 11 | 12 | if test "$PHP_ZLIB" != "no"; then 13 | for i in /usr /usr/local /opt; do 14 | if test -f $i/include/zlib/zlib.h; then 15 | ZLIB_DIR=$i 16 | fi 17 | done 18 | 19 | if test -z "$ZLIB_DIR"; then 20 | AC_MSG_ERROR([zlib not installed (http://www.zlib.org)]) 21 | fi 22 | 23 | PHP_ADD_LIBRARY_WITH_PATH(z,$ZLIB_DIR/lib, ZLIB_SHARED_LIBADD) 24 | PHP_ADD_INCLUDE($ZLIB_DIR/include) 25 | 26 | AC_MSG_RESULT([found in $ZLIB_DIR]) 27 | AC_DEFINE(HAVE_ZLIB,1,[libz found and included]) 28 | 29 | PHP_NEW_EXTENSION(zlib, zlib.c, $ext_shared) 30 | PHP_SUBST(ZLIB_SHARED_LIBADD) 31 | fi 32 | ``` 33 | 34 | config.m4文件很明显比你迄今为止使用的要大. 幸运的是, 它的语法非常的简单易懂并且如果你熟悉bash脚本, 对它也就不会陌生. 35 | 36 | 文件和第5章"你的第一个扩展"中第一次出现的一样, 都是以PHP_ARG_WITH()宏开始. 这个宏的行为和你用过的PHP_ARG_ENABLE()宏一样, 不过它将导致./configure时的选项是--with-extname/--without-extname而不再是--enable-extname/--disable-extname. 37 | 38 | 回顾这些宏, 它们的功能是等同的, 不同仅在于是否让终端用户给你的包一些暗示. 你可以在自己创建的私有扩展上使用任意一种方式. 不过, 如果你计划公开发布, 那就应该知道php正式的编码标准, 它指出enable/disable用于哪些不需要链接外部库的扩展, with/without则反之. 39 | 40 | 由于我们这里假设的扩展将链接zlib库, 因此你的config.m4脚本要以查找扩展源代码中将包含的zlib.h头文件. 这通过检查一些标准位置/usr, /usr/local, /opt中include/zlib目录下的zlib.h完成对其下两个目录的定位. 41 | 42 | 如果找到了zlib.h, 则将基路径设置到临时变量ZLIB_DIR中. 一旦循环完成, config.m4脚本检查ZLIB_DIR是否包含内容来确定是否找到了zlib.h. 如果没有找到, 则产生一个有意义的错误让用户知道./configure不能继续. 43 | 44 | 此刻, 脚本假设头文件存在, 对应的库也必须存在, 因此在下面的两行使用它修改构建环境, 最终增加-lz -L$ZLIB_DIR/lib到LDFLAGS以及-I$ZLIB_DIR/include到CFLAGS. 45 | 46 | 最终, 输出一个确认消息指示zlib安装已经找到, 并且在编译期间使用它的路径. config.m4的其他部分从前面部分的学习中你应该已经熟悉了. 为config.h定义一个#define, 定义扩展并指定它的源代码文件, 同时标识一个变量完成将扩展附加到构建系统的工作. 47 | 48 | ## 功能测试 49 | 50 | 迄今为止, 这个config.m4示例指示查找了需要的头文件. 尽管这已经够用了, 但它仍然不能确保产生的二进制正确的进行链接, 因为可能不存在匹配的库文件, 或者版本不正确. 51 | 52 | 最简单的检查zlib.h对应的libz.so库文件是否存在的方式就是检查文件是否存在: 53 | ```c 54 | if ! test -f $ZLIB_DIR/lib/libz.so; then 55 | AC_MSG_ERROR([zlib.h found, but libz.so not present!]) 56 | fi 57 | ``` 58 | 59 | 当然, 这仅仅是问题的一面. 如果安装了其他的同名库但和你要查找的库不兼容怎么办呢? 确保你的扩展可以成功编译的最好方式是测试找到的库实际编译所需的内容. 要这样做就需要在config.m4中PHP_ADD_LIBRARY_WITH_PATH调用之前加入下面代码: 60 | ```c 61 | PHP_CHECK_LIBRARY(z, deflateInit,,[ 62 | AC_MSG_ERROR([Invalid zlib extension, gzInit() not found]) 63 | ],-L$ZLIB_DIR/lib) 64 | ``` 65 | 66 | 这个工具宏将展开输出一个完整的程序, ./configure将尝试编译它. 如果编译成功, 表示第二个参数定义的符号在第一个参数指定的库中存在. 成功后, 第三个参数中指定的autoconf脚本将会执行; 失败后, 第四个参数中指定的autoconf脚本将执行. 在这个例子中, 第三个参数为空, 因为没有消息就是最好的消息(译注: 应该是unix哲学之一), 第五个参数也就是左后一个参数, 用于指定额外的编译器和链接器标记, 这里, 使用-L致命了一个额外的用于查找库的路径. 67 | 68 | ## 可选功能 69 | 70 | 那么现在你已经有正确的库和头文件了, 但依赖的是所安装库的哪个版本呢? 你可能需要某些功能或排斥某些功能. 由于这种类型的变更通常涉及到某些特定入口点的增加或删除, 因此可以重用PHP_CHECK_LIBRARY()宏来检查库的某些能力. 71 | ```c 72 | PHP_CHECK_LIBRARY(z, gzgets,[ 73 | AC_DEFINE(HAVE_ZLIB_GETS,1,[Having gzgets indicates zlib >= 1.0.9]) 74 | ],[ 75 | AC_MSG_WARN([zlib < 1.0.9 installed, gzgets() will not be available]) 76 | ],-L$ZLIB_DIR/lib) 77 | ``` 78 | 79 | ## 测试实际行为 80 | 81 | 可能知道某个符号存在也还不能确保你的代码正确编译; 某些库的特定版本可能存在bug需要运行一些测试代码进行检查. 82 | 83 | AC_TRY_RUN()宏可以编译一个小的源代码文件为可执行程序并执行. 依赖于传回给./configure的返回代码, 你的脚本可以设置可选的#define语句或直接输出消息(比如如果bug导致不能工作则提示升级)安全退出. 考虑下面的代码(摘自ext/standard/config.m4): 84 | 85 | ```c 86 | AC_TRY_RUN([ 87 | #include 88 | 89 | double somefn(double n) { 90 | return floor(n*pow(10,2) + 0.5); 91 | } 92 | int main() { 93 | return somefn(0.045)/10.0 != 0.5; 94 | } 95 | ],[ 96 | PHP_ROUND_FUZZ=0.5 97 | AC_MSG_RESULT(yes) 98 | ],[ 99 | PHP_ROUND_FUZZ=0.50000000001 100 | AC_MSG_RESULT(no) 101 | ],[ 102 | PHP_ROUND_FUZZ=0.50000000001 103 | AC_MSG_RESULT(cross compile) 104 | ]) 105 | AC_DEFINE_UNQUOTED(PHP_ROUND_FUZZ, $PHP_ROUND_FUZZ, 106 | [Is double precision imprecise?]) 107 | ``` 108 | 109 | 你可以看到, AC_TRY_RUN()的第一个参数是一块C语言代码, 它将被编译执行. 如果这段代码的退出代码是0, 位于第二个参数的autoconf脚本将被执行, 这种情况标识round()函数和期望一样工作, 返回0.5. 110 | 111 | 如果代码块返回非0值, 位域第三个参数的autoconf脚本将被执行. 第四个参数(最后一个)在php交叉编译时使用. 这种情况下, 尝试运行示例代码是没有意义的, 因为目标平台不同于扩展编译时使用的平台. 112 | 113 | 114 | ## links 115 | * [目录]() 116 | * 17.1 [autoconf](<17.1.md>) 117 | * 17.3 [强制模块依赖](<17.3.md>) 118 | -------------------------------------------------------------------------------- /17.3.md: -------------------------------------------------------------------------------- 1 | # 强制模块依赖 2 | 3 | 在php 5.1中, 扩展之间的内部依赖是可以强制性的. 由于扩展可以静态构建到php中, 也可以构建为共享对象动态加载, 因此强制依赖需要在两个地方实现. 4 | 5 | ## 配置时模块依赖 6 | 7 | 第一个位置是你在本章课程中刚刚看到的config.m4文件中. 你可以使用PHP_ADD_EXTENSION_DEP(extname, depname[ , optional])宏标识extname这个扩展依赖于depname这个扩展. 当extname以静态方式构建到php中时, ./configure脚本将使用这一行代码确认depname必须首先初始化. optional参数是一个标记, 用来标识depname如果也是静态构建的, 应该在extname之前加载, 不过它并不是必须的依赖. 8 | 9 | 这个宏的一个使用示例是pdo驱动, 比如pdo_mysql是可预知依赖于pdo扩展的: 10 | ```c 11 | ifdef([PHP_ADD_EXTENDION_DEP], 12 | [ 13 | PHP_ADD_EXTENSION_DEP(pdo_mysql, pdo) 14 | ]) 15 | ``` 16 | 17 | 要注意PHP_ADD_EXTENSION_DEP()宏被包裹到一个ifdef()结构中. 这是因为pdo和它的驱动在编译大于或等于5.0版本的php时都是存在的, 然而PHP_ADD_EXTENSION_DEP()宏是直到5.1.0版本才出现的. 18 | 19 | ## 运行时模块依赖 20 | 21 | 另外一个你需要注册依赖的地方是zend_module_entry结构体中. 考虑下面第5章中你定义的zend_module_entry结构体: 22 | 23 | ```c 24 | zend_module_entry sample_module_entry = { 25 | #if ZEND_MODULE_API_NO >= 20010901 26 | STANDARD_MODULE_HEADER, 27 | #endif 28 | PHP_SAMPLE_EXTNAME, 29 | php_sample_functions, 30 | NULL, /* MINIT */ 31 | NULL, /* MSHUTDOWN */ 32 | NULL, /* RINIT */ 33 | NULL, /* RSHUTDOWN */ 34 | NULL, /* MINFO */ 35 | #if ZEND_MODULE_API_NO >= 20010901 36 | PHP_SAMPLE_EXTVER, 37 | #endif 38 | STANDARD_MODULE_PROPERTIES 39 | }; 40 | ``` 41 | 42 | 增加运行时模块依赖信息就需要对STANDARD_MOUDLE_HEADER部分进行一些小修改: 43 | ```c 44 | zend_module_entry sample_module_entry = { 45 | #if ZEND_MODULE_API_NO >= 220050617 46 | STANDARD_MODULE_HEADER_EX, NULL, 47 | php_sample_deps, 48 | #elif ZEND_MODULE_API_NO >= 20010901 49 | STANDARD_MODULE_HEADER, 50 | #endif 51 | PHP_SAMPLE_EXTNAME, 52 | php_sample_functions, 53 | 54 | NULL, /* MINIT */ 55 | NULL, /* MSHUTDOWN */ 56 | NULL, /* RINIT */ 57 | NULL, /* RSHUTDOWN */ 58 | NULL, /* MINFO */ 59 | #if ZEND_MODULE_API_NO >= 20010901 60 | PHP_SAMPLE_EXTVER, 61 | #endif 62 | STANDARD_MODULE_PROPERTIES 63 | }; 64 | ``` 65 | 66 | 现在, 如果ZEND_MODULE_API_NO高于php 5.1.0 beta发布版, 则STANDARD_MODULE_HEADER(译注: 这里原著笔误为STANDARD_MODULE_PROPERTIES)将被替换为略微复杂的结构, 它将包含一个指向模块依赖信息的引用. 67 | 68 | 这个目标结构体可以在你的zend_module_entry结构体上面定义如下: 69 | ```c 70 | #if ZEND_MODULE_API_NO >= 220050617 71 | static zend_module_dep php_sample_deps[] = { 72 | ZEND_MODULE_REQUIRED("zlib") 73 | {NULL,NULL,NULL} 74 | }; 75 | #endif 76 | ``` 77 | 78 | 和zend_function_entry向量类似, 这个列表可以有多项依赖, 按照顺序进行检查. 如果尝试加载某个依赖模块未满足, Zend将会中断加载, 报告不满足依赖的名字, 这样, 终端用户就可以通过首先加载其他模块来解决问题. 79 | 80 | 81 | ## links 82 | * [目录]() 83 | * 17.2 [库的查找](<17.2.md>) 84 | * 17.4 [Windows方言](<17.4.md>) 85 | -------------------------------------------------------------------------------- /17.4.md: -------------------------------------------------------------------------------- 1 | # Windows方言 2 | 3 | >由于译者对windows环境不熟悉, 因此略过本节. 4 | 5 | 6 | ## links 7 | * [目录]() 8 | * 17.3 [强制模块依赖](<17.3.md>) 9 | * 17.5 [小结](<17.5.md>) 10 | -------------------------------------------------------------------------------- /17.5.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 如果你的扩展将在未知或不可控制的环境构建, 让它足够聪明以应付奇怪的环境就非常重要. 使用php提供的unix和windows上强有力的脚本能力, 你应该可以检测到麻烦并在未知的管理员需要电话求助之前给于她一个解决方案. 4 | 5 | 现在你已经有使用php api从头建立php扩展的基础能力了, 你可以准备学习一下使用php提供的扩展开发工具把自己从繁重的重复劳动中解放出来了, 使用它们可以快速, 准确的建立新扩展的原型. 6 | 7 | 8 | ## links 9 | * [目录]() 10 | * 17.4 [Windows方言](<17.4.md>) 11 | * 18 [扩展生成](<18.md>) 12 | -------------------------------------------------------------------------------- /17.md: -------------------------------------------------------------------------------- 1 | # 17 配置和链接 2 | 3 | ##目录 4 | 5 | * [1. autoconf](17.1.md) 6 | * [2. 库的查找](17.2.md) 7 | * [3. 强制模块依赖](17.3.md) 8 | * [4. Windows方言](17.4.md) 9 | * [5. 小结](<17.5.md>) 10 | 11 | 所有前面示例中的代码, 都是你曾经在php用户空间编写过代码的C语言的独立版本. 如果你做的项目需要和php扩展进行粘合, 那么你就至少需要链接一个外部库. 12 | 13 | 14 | ## links 15 | * [目录]() 16 | * 16.3 [小结](<16.3.md>) 17 | * 17.1 [autoconf](<17.1.md>) 18 | -------------------------------------------------------------------------------- /18.1.md: -------------------------------------------------------------------------------- 1 | # ext_skel 2 | 3 | 切换到你的php源代码树下ext/目录中, 执行下面的命令: 4 | 5 | ````c 6 | jdoe@devbox:/home/jdoe/cvs/php-src/ext/$ ./ext_skel extname=sample7 7 | ```` 8 | 9 | 稍等便可, 输出⼀些文本, 你将看到下面的这些输出: 10 | 11 | ````c 12 | To use your new extension, you will have to execute the following steps: 1. $cd.. 2. $ vi ext/sample7/config.m4 3. $ ./buildconf 4. $ ./configure [with|enable]-sample7 5. $ make 6. $ ./php -f ext/sample7/sample7.php 7. $ vi ext/sample7/sample7.c 8. $ make Repeat steps 3-6 until you are satisfied with ext/sample7/config.m4 and step 6 confirms that your module is compiled into PHP. Then, start writing code and repeat the last two steps as often as necessary. 13 | ```` 14 | 15 | 此刻观察ext/sample7目录, 你将看到在第5章"你的第一个扩展"中你编写的扩展骨架 代码的注释版本. 只是现在你还不能编译它; 不过只需要对config.m4做少许修改就可以让 它工作了, 这样你就可以避免第5章中你所做的大部分工作.生成函数原型 16 | ####生成函数原型 17 | 如果你要编写一个对第三方库的包装扩展, 那么你就已经有了⼀个函数原型及基本行 为的机器刻度版本的描述(头文件), 通过传递一个额外的参数给./ext_skel, 它将自动的扫 描你的头文件并创建对应于接口的简单PHP_FUCNTION()块. 下面是使用./ext_skel指令 解析zlib头: 18 | ````c 19 | jdoe@devbox:/home/jdoe/cvs/php-src/ext/$ ./ext_skel extname=sample8 \ proto=/usr/local/include/zlib/zlib.h 20 | ```` 21 | 22 | 现在在ext/sample8/sample8.c中, 你就可以看到许多PHP_FUNCTION()定义, 每个 zlib函数对应一个. 要注意, 骨架生成程序会对某些未知资源类型产生警告消息. 你需要对 这些函数特别注意, 并且为了将这些内部的复杂结构体和用户空间可访问的变量关联起来, 可能会需要使用你在第9章"资源数据类型"中学到的知识. 23 | 24 | ## links 25 | * [目录]() 26 | * 18 [扩展生成](<18.md>) 27 | * 18.2 [PECL_Gen](<18.2.md>) 28 | -------------------------------------------------------------------------------- /18.2.md: -------------------------------------------------------------------------------- 1 | # PECL_Gen 2 | 3 | 还有一种更加完善但也更加复杂的代码生成器: PECL_Gen, 可以在PECL(http:// pecl.php.net)中找到它, 使用pear install PECL_Gen命令可以安装它. 4 | 5 | ````c 6 | 译者注: PECL_Gen已经迁移为CodeGen_PECL(http://pear.php.net/package/ CodeGen_PECL). 本章涉及代码测试使用CodeGen_PECL的版本信息为: "php 1.1.3, Copyright (c) 2003-2006 Hartmut Holzgraefe", 如果您的环境使用有问题, 请参考译序中译者的环境配置. 7 | ```` 8 | 9 | ⼀旦安装完成, 它就可以像ext_skel一样运行, 接受相同的输入参数, 产生大致相同的 输出, 或者如果提供了一个完整的xml定义文件, 则产生一个更加健壮和完整可编译版本的 扩展. PECL_Gen并不会节省你编写扩展核心功能的时间; 而是提供⼀种可选的方式高效 的生成扩展骨架代码. 10 | 11 | #### specfile.xml 12 | 13 | 下面是最简单的扩展定义文件: 14 | 15 | ````c 16 | 译注: 请注意, 译者使用的原著中第一行少了后面的问号, 导致不能使用, 加上就OK. 17 | ```` 18 | 19 | 通过PECL_Gen命令运行这个文件: 20 | 21 | ````c 22 | jdoe@devbox:/home/jdoe/cvs/php-src/ext/$ pecl-gen specfile.xml 23 | ```` 24 | 25 | 则会产生一个名为sample9的扩展, 并暴露一个用户空间函数sample9_hello_world(). 26 | 27 | ####关于扩展 28 | 29 | 除了你已经熟悉的功能文件, PECL_Gen还会产生⼀个package.xml文件 它可以用于 pear安装. 如果你计划发布包到PECL库, 或者哪怕你只是想要使用pear包系统交付内容, 有这个文件都会很有用. 30 | 31 | 总之, 你可以在PECL_Gen的specfile.xml中指定多数package.xml文件的元素. 32 | 33 | ````c 34 | Extension 9 generated by PECL_Gen Another sample of PHP Extension Writing John D. Bookreader jdb@example.com lead 0.1 2006-01-01 beta Initial Release ... 35 | ```` 36 | 37 | 当PECL_Gen创建扩展时, 这些信息将被翻译到最终的package.xml文件中. 依赖 38 | 39 | #### 依赖 40 | 41 | 如你在第17章"配置和链接"中所见, 依赖可以扫描出来用于config.m4和config.w32文 件. PECL_Gen可以使用定义各种类型的依赖完成扫描工作. 默认情况下, 列在 标签下的依赖会同时应用到Unix和win32构建中, 除非显式的是否用platform属性 指定某个目标 42 | 43 | ````c 44 | ... ... 45 | ```` 46 | 47 | #### with 48 | 49 | 通常, 扩展在配置时使用--enable-extname样式的配置选项. 通过增加⼀个或多个 标签到块中, 则不仅配置选项被修改为--with-extname, 而且同时需要扫描 头文件: 50 | 51 | ````c 52 | zlib headers 53 | ```` 54 | 55 | #### 库 56 | 57 | 必须的库也列在下, 使用标签. 58 | 59 | ````c 60 | 61 | ```` 62 | 63 | 在前面两个例子中, 只是检查了库是否存在; 第三个例子中, 库将被真实的加载并扫描 以确认inflate()函数是否定义. 64 | 65 | 尽管标签实际已经命名了目标平台, 但标签也有⼀个platform属性可以覆盖 标签的platform设置. 当它们混合使用的时候要格外小心. 66 | 67 | ####
68 | 69 | 此外, 需要包含的文件也可以通过在块中使用
标签在你的代码中追 加⼀个#include指令列表. 要强制某个头先包含, 可以在
标签上增加属性 prepend="yes". 和依赖类似,
也可以严格限制平台: 70 | 71 | ````c 72 |
译注: 经测试, 译者的环境
标签不支持platform属性. 73 | ```` 74 | 75 | #### 常量 76 | 77 | 用户空间常量使用块中的一个或多个标签定义. 每个标签需 要一个name和⼀个value属性, 以及⼀个值必须是int, float, string之一的type属性. 78 | 79 | ````c 80 | 81 | ```` 82 | 83 | #### 全局变量 84 | 85 | 线程安全全局变量的定义方式几乎相同. 唯⼀的不同在于type参数需要使用C语言原 型而不是php用户空间描述. ⼀旦定义并构建, 全局变量就可以使用第12章"启动, 终止, 以 及其中的⼀些点"中学习的EXTNAME_G(global_name)的宏用法进行访问. 在这里, value属性表示变量在请求启动时的默认值. 要注意在specfile.xml中这个默认值只能指定为简单 的标量数值. 字符串和其他复杂结构应该在RINIT阶段手动设置. 86 | 87 | ````c 88 |  89 | ```` 90 | 91 | #### INI选项 92 | 93 | 要绑定线程安全的全局变量到php.ini设置, 则需要使用标签而不是. 这个标签需要两个额外的参数: onupdate="updatemethod"标识INI的修改应该怎样处理, access="mode"和第13章"INI设置"中介绍的模式含义相同, "mode"值可以是: all, user, perdir, system. 94 | 95 | ````c 96 |  97 | ```` 98 | 99 | #### 函数 100 | 101 | 你已经看到了最基本的函数定义; 不过, 标签在PECL_Gen的specfile中实 际上支持两种不同类型的函数. 102 | 103 | 两个版本都支持你已经在级别上使用过的属 性; 两种类型都必须的元素是标签, 它包含了将要被放入你的源代码文件中的原文C语言代码. 104 | 105 | #### role="public" 106 | 107 | 如你所想, 所有定义为public角色的函数都将包装恰当的PHP_FUNCTION()头和花括 号, 对应到扩展的函数表向量中的条目. 108 | 109 | 除了其他函数支持的标签, public类型还允许指定一个标签. 这个标签的格式 应该匹配php在线手册中的原型展示, 它将被文档生成器解析. 110 | 111 | ````c 112 | Greet a person by name Accept a name parameter as a string and say hello to that person. Returns TRUE. bool sample9_greet_me(string name) 113 | ```` 114 | 115 | #### role="internal" 116 | 117 | 内部函数涉及5个zend_module_entry函数: MINIT, MSHUTDOWN, RINIT, RSHUTDOWN, MINFO. 如果指定的名字不是这5个之一将会产生pecl-gen无法处理的错误. 118 | 119 | ````c 120 | 121 | ```` 122 | 123 | #### 自定义代码 124 | 125 | 所有其他需要存在于你的扩展中的代码都可以使用标签包含. 要放置任意代码 到你的目标文件extname.c中, 使用role="code"; 或者说使用role="header"将代码放到目标 文件php_extname.h中. 默认情况下, 代码将放到代码或头文件的底部, 除非指定了 position="top"属性. 126 | 127 | ````c 128 | val = value; return ret; } ]]> 译注: 译者的环境中不支持原著中标签的name属性. 129 | ```` 130 | 131 | ## links 132 | * [目录]() 133 | * 18.1 [ext_skel](<18.1.md>) 134 | * 18.3 [小结](<18.3.md>) 135 | -------------------------------------------------------------------------------- /18.3.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 使用本章讨论的工具, 你就可以快速的开发php扩展, 并且让你的代码相比手写更加不 容易产生bug. 现在是时候转向将php嵌入到其他项目了. 剩下的章节中, 你将利用php环境 和强大的php引擎为你的已有项目增加脚本能力, 使它可以为你的客户提供更多更有用的 功能. 4 | 5 | 6 | ## links 7 | * [目录]() 8 | * 18.2 [PECL_Gen](<18.2.md>) 9 | * 19 [设置宿主环境](<19.md>) 10 | -------------------------------------------------------------------------------- /18.md: -------------------------------------------------------------------------------- 1 | # 18 扩展生成 2 | 3 | ##目录 4 | 5 | * [1. ext_skel](18.1.md) 6 | * [2. PECL_Gen](18.2.md) 7 | * [3. 小结](18.3.md) 8 | 9 | 毫无疑问你已经注意到,每个php扩展都包含一些非常公共的并且非常单调的结构和文件。当开始一个新扩展开发的时候,如果这些公共的结构已经存在, 我们只用考虑填充功 能代码是很有意义的. 为此, 在php中包含了一个简单但是很有用的shell脚本。 10 | 11 | 12 | ## links 13 | * [目录]() 14 | * 17.3 [小结](<17.3.md>) 15 | * 18.1 [ext_skel](<18.1.md>) 16 | -------------------------------------------------------------------------------- /19.1.md: -------------------------------------------------------------------------------- 1 | # 嵌入式SAPI 2 | 3 | 回顾介绍中, php构建了一个层级系统. 最高层是提供用户空间函数和类库的所有扩 展. 同时, 其下是服务API(SAPI)层, 它扮演了webserver(比如apache, iis以及命令行接口 cli)的接口. 4 | 在这许多sapi实现中有一个特殊的sapi就是嵌入式sapi. 当这个sapi实现被构建时, 将 会创建一个包含所有你已知的php和zend api函数以及变量的库对象, 这个库对象还包含一些额外的帮助函数和宏, 用以简化外部程序的调用. 5 | 生成嵌入式api的库和头文件和其他sapi的编译所执行的动作相同. 只需要传递--enable-embed到./configure命令中即可. 和以前⼀样, 使用--enable-debug对于错误报告和 跟踪很有帮助. 6 | 你可能还需要打开--enable-maintainer-zts, 当然, 理由你已经耳熟能详了, 它将帮助 你注意到代码的错误, 不过, 这里还有其他原因. 假设某个时刻, 你有多个应用使用php嵌入 库执行脚本任务; 其中一个应用是简单的短生命周期的, 它并没有使用线程, 因此为了效率 你可能想要关闭ZTS. 7 | 现在假设第二个应用使用了线程, 比如webserver, 每个线程需要跟踪自己的请求上下 文. 如果ZTS被关闭, 则只有第⼀个应用可以使用这个库; 然而, 如果打开ZTS, 则两个应用 都可以在自己的进程空间使用同⼀个共享对象. 8 | 当然, 你也可以同时构建两个版本, 并给它们不同的名字, 但是这相比于在不需要ZTS 时包括ZTS带来的很小的效率影响更多的问题. 默认情况下, 嵌入式库将构建为libphp5.so共享对象, 或者在windows下的动态链接库, 不过, 它也可能使用可选的static关键字(--enable-embed=static)被构建为静态库. 9 | 构建为静态库的版本避免了ZTS/非ZTS的问题, 以及潜在的可能在一个系统中有多个 php版本的情况. 风险在于这就意味着你的结果应用二进制将显著变大, 它将承载整个 ZendEngine和PHP框架, 因此, 选择的时候就需要慎重的考虑你是否需要的是⼀个相对更小的库. 10 | 无论你选择那种构建方式,一旦你执行make install, libphp5都将被拷贝到你的./ configure指定的PREFIX目录下的lib/目录中. 此外还会在PREFIX/include/php/sapi/ embed目录下放入名为php_embed.h的头文件, 以及你在使用php嵌入式库编译程序时需 要的其他几个重要的头文件. 11 | 12 | ## links 13 | * [目录]() 14 | * 19 [设置宿主环境](<19.md>) 15 | * 19.2 [构建并编译⼀个宿主应用](<19.2.md>) 16 | -------------------------------------------------------------------------------- /19.2.md: -------------------------------------------------------------------------------- 1 | # 构建并编译一个宿主应用 2 | 3 | 究其本质而言, 库只是⼀个没有目的的代码集合. 为了让它工作, 你需要用以嵌入php 的应用. 首先, 我们来封装⼀个非常简单的应用, 它启动Zend引擎并初始化PHP处理⼀个请求, 接着就回头进行资源的清理. 4 | 5 | ````c 6 | #include int main(int argc, char *argv[]) { PHP_EMBED_START_BLOCK(argc,argv) PHP_EMBED_END_BLOCK() return 0; } 7 | ```` 8 | 9 | 由于这涉及到了很多头文件, 构建实际上需要的时间要长于这么小的代码片段通常需 要的时间. 如果你使用了不同于默认路径(/usr/local)的PREFIX, 请确认以下面的方式指定 路径: 10 | 11 | ````c 12 | gcc -I /usr/local/php-dev/include/php/ \ -I /usr/local/php-dev/include/php/main/ \ -I /usr/local/php-dev/include/php/Zend/ \ -I /usr/local/php-dev/include/php/TSRM/ \ -lphp5 \ -o embed1 embed1.c 13 | ```` 14 | 15 | 由于这个命令每次输入都很麻烦, 你可能更原意用一个简单的Makefile替代: 16 | 17 | ````c 18 | CC = gcc CFLAGS = -c \ -I /usr/local/php-dev/include/php/ \ -I /usr/local/php-dev/include/php/main/ \ -I /usr/local/php-dev/include/php/Zend/ \ -I /usr/local/php-dev/include/php/TSRM/ \ -Wall -g LDFLAGS = -lphp5 all: embed1.c $(CC) -o embed1.o embed1.c $(CFLAGS) $(CC) -o embed1 embed1.o $(LDFLAGS) 19 | ```` 20 | 21 | 这个Makefile和前面提供的命令有⼀些重要的区别. 首先, 它用-Wall开关打开了编译期的警 告, 并且用-g打开了调试信息. 此外它将编译和链接两个阶段分为了两个独立的阶段, 这样在后期 增加更多源文件的时候就相对容易. 请自己重新组着这个Makefile, 不过这里用于对齐的是Tab(水 平制表符)而不是空格. 22 | 23 | 现在, 你对embed1.c源文件做修改后, 只需要执行⼀一个make命令就可以构建出新的 embed1可执行程序了. 24 | 25 | ## links 26 | * [目录]() 27 | * 19.1 [嵌入式SAPI](<19.1.md>) 28 | * 19.3 [通过嵌入包装重新创建cli](<19.3.md>) 29 | -------------------------------------------------------------------------------- /19.3.md: -------------------------------------------------------------------------------- 1 | # 通过嵌入包装重新创建cli 2 | 3 | 现在php已经可以在你的应用中访问了, 是时候让它做⼀些事情了. 本章剩下的核心就是围绕着在这个测试应用框架中重新创建cli sapi展开的. 4 | 5 | 很简单, cli二进制程序最基础的功能就是在命令行指定⼀个脚本的名字, 由php对其解 释执行. 用下面的代码替换你的embed1.c的内容就在你的应用中实现了cli. 6 | 7 | ````c 8 | #include #include int main(int argc, char *argv[]) { zend_file_handle script; /* 基本的参数检查 */ if ( argc <= 1 ) { fprintf(stderr, "Usage: %s \n", argv[0]); return -1; } /* 设置⼀一个文件处理结构 */ script.type script.filename script.opened_path script.free_filename if ( !(script.handle.fp = fopen(script.filename, "rb")) ) { fprintf(stderr, "Unable to open: %s\n", argv[1]); return -1; } /* 在将命令行参数注册给php时(php中的$argv/$argc), 忽略第一个命令行参数, 因为它对php脚本无意义 */ argc --; argv ++; PHP_EMBED_START_BLOCK(argc, argv) php_execute_script(&script TSRMLS_CC); PHP_EMBED_END_BLOCK() return 0; } 译注: 原著中的代码在译者的环境不能直接运行, 上面的代码是经过修改的. 9 | ```` 10 | 11 | 当然, 你需要⼀个文件测试它, 创建⼀个小的php脚本, 命名为test.php, 在命令行使用你的embed程序执行它: 12 | 13 | ````c 14 | $ ./embed1 test.php 15 | ```` 16 | 17 | 如果你给命令行传递了其他参数, 你可以在你的php脚本中使用$_SERVER['argc']/ $_SERVER['argv']看到它们. 18 | 19 | 你可能注意到了, 在PHP_EMBED_START_BLOCK()和PHP_EMBED_END_BLOCK()之间 的代码是缩进的. 这个细节是因为这两个宏实际上构成了⼀个C语言的代码块作用域. 也就是说 PHP_EMBED_START_BLOCK()包含⼀个打开的花括号"{", 在PHP_EMBED_END_BLOCK()中 则有与之对应的关闭花括号"}". 这样做非常重要的一个问题是它们不能被放入到独立的启动/终止函数中. 下一章你将看到这个问题的解决方案. 20 | 21 | ## links 22 | * [目录]() 23 | * 19.2 [构建并编译一个宿主应用](<19.2.md>) 24 | * 19.4 [老技术新用](<19.4.md>) 25 | -------------------------------------------------------------------------------- /19.4.md: -------------------------------------------------------------------------------- 1 | # 老技术新用 2 | 3 | 在PHP_EMBED_START_BLOCK()被调用后, 你的应用处于⼀个php请求周期的开始 位置, 相当于RINIT回调函数完成以后. 此刻你就可以和前面一样执行 php_execute_script()命令, 或者其他任意合法的, 可以在PHP_FUNCTION()或RINIT()块中出现的php/Zend API指令. 4 | 5 | #### 设置初始变量 6 | 7 | 第2章"变量的里里外外"中介绍了操纵符号表的概念, 第5至18章则介绍了怎样通过用 户空间脚本调用内部函数使用这些技术. 到这里这些处理也并没有发生变化, 虽然这里并 没有激活的用户空间脚本, 但是你的包装应用仍然可以操纵符号表. 将你的 PHP_EMBED_START_BLOCK()/PHP_EMBED_END_BLOCK()代码块替换为下面的代码: 8 | 9 | ````c 10 | PHP_EMBED_START_BLOCK(argc, argv) zval *type; ALLOC_INIT_ZVAL(type); ZVAL_STRING(type, "Embedded", 1); ZEND_SET_SYMBOL(&EG(symbol_table), "type", type); php_execute_script(&script TSRMLS_CC); PHP_EMBED_END_BLOCK() 11 | ```` 12 | 13 | 现在使用make重新构建embed1, 并用下面的测试脚本进行测试: 14 | 15 | ````c 16 | 19 | ```` 20 | 21 | 当然, 这个简单的概念可以很容易的扩展为填充这个类型信息到$_SERVER超级全局变量数组中. 22 | 23 | ````c 24 | PHP_EMBED_START_BLOCK(argc, argv) zval **SERVER_PP, *type; /* 注册$_SERVER超级全局变量 */ zend_is_auto_global_quick("_SERVER", sizeof("_SERVER") - 1, 0 TSRMLS_CC); 25 | /* 查找$_SERVER超级全局变量 */ 26 | zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&SERVER_PP) ; /* $_SERVER['SAPI_TYPE'] = "Embedded"; */ ALLOC_INIT_ZVAL(type); ZVAL_STRING(type, "Embedded", 1); ZEND_SET_SYMBOL(Z_ARRVAL_PP(SERVER_PP), "SAPI_TYPE", type); php_execute_script(&script TSRMLS_CC); PHP_EMBED_END_BLOCK() 译注: 译者的环境中代码运行到zend_hash_find()处$_SERVER尚未注册, 经过跟踪, 发现它 是直到编译用户空间代码的时候, 发现用户空间使用了$_SERVER变量才进行的注册. 因此, 上面 的代码中增加了zend_is_auto_global_quick()的调用, 通过这个调用将完成对$_SERVER的注册. 27 | ```` 28 | 29 | #### 覆写INI选项 30 | 31 | 在第13章"INI设置"中, 有⼀部分是讲INI修改处理器的, 在那里看到的是INI阶段的处 理. PHP_EMBED_START_BLOCK()宏则将这些代码放到了运行时阶段. 也就是说这个时 候修改某些设置(比如register_globals/magic_quotes_gpc)已经有点迟了. 32 | 33 | 不过在内部访问也没有什么不好. 所谓的"管理设置"比如safe_mode在这个略迟的阶 段可以使用下面的zend_alter_ini_entry()命令打开或关闭: 34 | 35 | ````c 36 | int zend_alter_ini_entry(char *name, uint name_length, char *new_value, uint new_value_length, int modify_type, int stage); 37 | ```` 38 | 39 | name, new_value以及它们对应的长度参数的含义正如你所预期的: 修改名为name的 INI设置的值为new_value. 要注意name_length包含了末尾的NULL字节, 然而 new_value_length则不包含; 然而, 无论如何, 两个字符串都必须是NULL终止的. 40 | modify_type则提供简化的访问控制检查. 回顾每个INI设置都有一个modifiable属性, 它是PHP_INI_SYSTEM, PHP_INI_PERDIR, PHP_INI_USER等常量的组合值. 当使用 zend_alter_ini_entry()修改INI设置时, modify_type参数必须包含至少⼀个INI设置的 modifiable属性值. 41 | 用户空间的ini_set()函数通过传递PHP_INI_USER利用了这个特性, 也就是说只有 modifiable属性包含PHP_INI_USER标记的INI设置才能使用这个函数修改. 当在你的嵌入 式应用中使用这个API调用时, 你可以通过传递PHP_INI_ALL标记短路这个访问控制系统, 它将包含所有的INI访问级别. 42 | stage必须对应于Zend Engine的当前状态; 对于这些简单的嵌入式示例, 总是 PHP_INI_STAGE_RUNTIME. 如果这是一个扩展或更高端的嵌入式应用, 你可能就需要将 这个值设置为PHP_INI_STAGE_STARTUP或PHP_INI_STAGE_ACTIVE. 43 | 下面是扩展embed1.c源文件, 让它在执行脚本文件之前强制开启safe_mode. ````c 44 | PHP_EMBED_START_BLOCK(argc, argv) zval **SERVER_PP, *type; /* 不论php.ini中如何设置都强制开启safe_mode */ zend_alter_ini_entry("safe_mode", sizeof("safe_mode"), "1", sizeof("1") - 1, PHP_INI_ALL, PHP_INI_STAGE_RUNTIME); /* 注册$_SERVER超级全局变量 */ zend_is_auto_global_quick("_SERVER", sizeof("_SERVER") - 1, 0 TSRMLS_CC); /* 查找$_SERVER超级全局变量 */ zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&SERVER_PP) ; /* $_SERVER['SAPI_TYPE'] = "Embedded"; */ ALLOC_INIT_ZVAL(type); ZVAL_STRING(type, "Embedded", 1); ZEND_SET_SYMBOL(Z_ARRVAL_PP(SERVER_PP), "SAPI_TYPE", type); php_execute_script(&script TSRMLS_CC); 45 | PHP_EMBED_END_BLOCK() ```` 46 | #### 定义附加的超级全局变量 47 | 在第12章"启动, 终止, 以及其中的一些点"中, 你知道了用户空间全局变量以及超级全 局变量可以在启动(MINIT)阶段定义. 同样, 本章介绍的嵌入式直接跳过了启动阶段, 处于 运行时状态. 和覆写INI一样, 这并不会显得太迟. 48 | 超级全局变量的定义实际上只需要在脚本编译之前定义即可, 并且在php的进程生命 周期中它只应该出现⼀次. 在扩展中的正常情况下, MINIT是唯一可以保证这些条件的地方. 49 | 由于你的包装应用现在是在控制中的, 因此可以保证定义用户空间自动全局变量的这 些点位于真正编译脚本源文件的php_execute_script()命令之前. 我们定义⼀个$_EMBED 超级全局变量并给它设置一个初始值来进行测试: 50 | 51 | ````c 52 | HP_EMBED_START_BLOCK(argc, argv) zval **SERVER_PP, *type, *EMBED, *foo; /* 在全局作用域创建$_EMBED数组 */ ALLOC_INIT_ZVAL(EMBED); array_init(EMBED); ZEND_SET_SYMBOL(&EG(symbol_table), "_EMBED", EMBED); /* $_EMBED['foo'] = 'Bar'; */ ALLOC_INIT_ZVAL(foo); ZVAL_STRING(foo, "Bar", 1); add_assoc_zval_ex(EMBED, "foo", sizeof("foo"), foo); /* 注册超级全局变量$_EMBED */ zend_register_auto_global("_EMBED", sizeof("_EMBED") #ifdef ZEND_ENGINE_2 #else #endif , 1, NULL TSRMLS_CC); , 1 TSRMLS_CC); /* 不论php.ini中如何设置都强制开启safe_mode */ zend_alter_ini_entry("safe_mode", sizeof("safe_mode"), "1", sizeof("1") - 1, PHP_INI_ALL, PHP_INI_STAGE_RUNTIME); /* 注册$_SERVER超级全局变量 */ zend_is_auto_global_quick("_SERVER", sizeof("_SERVER") - 1, 0 TSRMLS_CC); /* 查找$_SERVER超级全局变量 */ zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&SERVER_PP) ; /* $_SERVER['SAPI_TYPE'] = "Embedded"; */ ALLOC_INIT_ZVAL(type); ZVAL_STRING(type, "Embedded", 1); ZEND_SET_SYMBOL(Z_ARRVAL_PP(SERVER_PP), "SAPI_TYPE", type); php_execute_script(&script TSRMLS_CC); PHP_EMBED_END_BLOCK() 53 | ```` 54 | 55 | 要记住, Zend Engine 2(php 5.0或更高)使用了不同的zend_register_auto_global()元婴, 因此你需要用前面讲php 4兼容时候讲过的#ifdef. 如果你不关心旧版本php的兼容性, 则可以丢弃这些指令让代码变得更加整洁. 56 | 57 | 58 | ## links 59 | * [目录]() 60 | * 19.3 [通过嵌入包装重新创建cli](<19.3.md>) 61 | * 19.5 [小结](<19.5.md>) 62 | -------------------------------------------------------------------------------- /19.5.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 如你所见, 将完整的Zend Engine和PHP语言嵌入到你的应用中相比如扩展新功能来 说工作量要少. 由于它们共享相同的基础API, 我们可以学习尝试让其他实例可访问. 通过本章的学习, 你了解了最简单的嵌入式脚本代码格式, 同时还有all-in-one的宏 PHP_EBED_START_BLOCK()和PHP_EMBED_END_BLOCK(). 下⼀章你将回到这些宏 的层的使用, 利用它们将php和你的宿主系统结合起来. 4 | 5 | 6 | ## links 7 | * [目录]() 8 | * 19.4 [老技术新用](<19.4.md>) 9 | * 20 [高级嵌入式](<20.md>) 10 | -------------------------------------------------------------------------------- /19.md: -------------------------------------------------------------------------------- 1 | # 19 设置宿主环境 2 | 3 | ##目录 4 | 5 | * [1. 嵌入式SAPI](19.1.md) 6 | * [2. 构建并编译一个宿主应用](19.2.md) 7 | * [3. 通过嵌入包装重新创建cli](19.3.md) 8 | * [4. 老技术新用](19.4.md) 9 | * [5. 小结](19.5.md) 10 | 11 | 12 | 现在你已经了解了PHPAPI的世界, 并可以使用zval以及语言内部扩展机制执行很多 工作了, 是时候转移目标用它做它最擅长的事情了: 解释脚本代码. 13 | 14 | 15 | ## links 16 | * [目录]() 17 | * 18.3 [小结](<18.3.md>) 18 | * 19.1 [嵌入式SAPI](<19.1.md>) 19 | -------------------------------------------------------------------------------- /2.3.md: -------------------------------------------------------------------------------- 1 | # 2.3 创建PHP变量 2 | 3 | ````c 4 | zval zv;//这与PHP5中创建变量有很大的不同 PHP7中删除了ALLOC_ZVAL、ALLOC_INIT_ZVAL、MAKE_STD_ZVAL、INIT_PZVAL等 5 | ZVAL_LONG(&zv, 0); 6 | ```` 7 | 在PHP5的Zend Engine的实现中,所有的值都是在堆上分配空间,并且通过引用计数和垃圾收集来管理. PHP5的Zend Engine主要使用指向zval结构的指针来操作值,在很多地方甚至通过zval的二级指针来操作. 而在PHP7的Zend Engine实现中,值是通过zval结构本身来操作(非指针). 新的zval结构直接被存放在VM的栈上,HashTable的桶里,以及属性槽里. 这样大大减少了在堆上分配和释放内存的操作,还避免了对简单值的引用计数和垃圾收集.(转) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
其它宏的实现方法
ZVAL_NULL(pvz); ** (注意这个Z和VAL之间没有下划线!) ** Z_TYPE_INFO_P(pzv) = IS_NULL;**(IS_NULL型不用赋值,因为这个类型只有一个值就是null,^_^)**
ZVAL_BOOL(pzv, b); **(将pzv所指的zval设置为IS_TRUE或IS_FALSE类型,值是b)**Z_TYPE_INFO_P(z) = (b) ? IS_TRUE : IS_FALSE;
ZVAL_TRUE(pzv); **(将pzv所指的zval设置为IS_TRUE类型 )**Z_TYPE_INFO_P(z) = IS_TRUE;
ZVAL_FALSE(pzv); **(将pzv所指的zval设置为IS_FALSE类型)**Z_TYPE_INFO_P(z) = IS_FALSE
ZVAL_LONG(pzv, l); **(将pzv所指的zval设置为IS_LONG类型,值是l)**zval *__z = (z); Z_LVAL_P(__z) = l; Z_TYPE_INFO_P(__z) = IS_LONG;
ZVAL_DOUBLE(pzv, d); **(将pzv所指的zval设置为IS_DOUBLE类型,值是d)**Z_TYPE_Pzval *__z = (z); Z_DVAL_P(__z) = d; Z_TYPE_INFO_P(__z) = IS_DOUBLE;
ZVAL_STRINGL(pzv,str,len);ZVAL_NEW_STR(pzv, zend_string_init(str, len, 0)
ZVAL_STRING(pzv, str);const char *_s = (str); ZVAL_STRINGL(pzv, _s, strlen(_s));
ZVAL_RES(pzv, res);zval *__z = (pzv);Z_RES_P(__z) = (res);Z_TYPE_INFO_P(__z) = IS_RESOURCE_EX;
53 | 54 | 55 | ### ZVAL_STRINGL与ZVAL_STRING的区别 56 | 57 | 如果你想在某一位置截取该字符串或已经知道了这个字符串的长度, 58 | 那么可以使用宏 ZVAL_STRINGL(zval, string, length) ,它显式的指定字符串长度, 59 | 而不是使用strlen()。这个宏该字符串长度作为参数。但它是二进制安全的,而且速度也比ZVAL_STRING快,因为少了个strlen。 60 | 61 | 62 | ## links 63 | * [目录]() 64 | * 2.2 [变量的值](<2.2.md>) 65 | * 2.4 [变量的存储方式](<2.4.md>) 66 | 67 | -------------------------------------------------------------------------------- /2.4.md: -------------------------------------------------------------------------------- 1 | # 2.4 变量的存储方式 2 | 3 | (php5) 4 | 5 | 我们在前两节已经了解了PHP中变量的类型和值是怎样在内核中用C语言实现的, 6 | 这一节我们将看一下内核是怎样来组织用户在PHP中定义的变量的。 7 | 8 | 有一点对我们扩展开发者来说非常棒,那就是用户在PHP中定义的变量我们都可以在一个HashTable中找到, 9 | 当PHP中定义了一个变量,内核会自动的把它的信息储存到一个用HashTable实现的符号表里。 10 | 11 | 全局作用域的符号表是在调用扩展的RINIT方法(一般都是MINIT方法里)前创建的,并在RSHUTDOWN方法执行后自动销毁。 12 | 13 | 当用户在PHP中调用一个函数或者类的方法时,内核会创建一个新的符号表并激活之, 14 | 这也就是为什么我们无法在函数中使用在函数外定义的变量的原因 15 | (因为它们分属两个符号表,一个当前作用域的,一个全局作用域的)。 16 | 如果不是在一个函数里,则全局作用域的符号表处于激活状态。 17 | 18 | 我们现在打开Zend/zend_globals.h文件,看一下_zend_execution_globals结构体,会在其中发现这么两个element: 19 | 20 | ````c 21 | struct _zend_executor_globals { 22 | ... 23 | HashTable symbol_table; 24 | HashTable *active_symbol_table; 25 | ... 26 | }; 27 | 28 | ```` 29 | 30 | 其中的 symbol_table元素可以通过EG宏来访问,它代表着PHP的全局变量,如$GLOBALS,其实从根本上来讲, 31 | $GLOBALS不过是EG(symbol_table)的一层封装而已。 32 | 33 | 与之对应,下面的active_symbol_table元素也可以通过EG(active_symbol_table)的方法来访问,它代表的是处于当前作用域的变量符号表。 34 | 35 | 我们上边也看到了,其实这两个成员在_zend_executor_globals里虽然都代表HashTable, 36 | 但一个是真正的HashTable,而另一个是一个指针。 37 | 当我们在对HashTable进行操作的时候,往往是把它的地址传递给一些函数。 38 | 所以,如果我们要对EG(symbol_table)的结果进行操作,往往需要对它进行求址操作然后用它的地址作为被调用函数的参数。 39 | 40 | 下面我们用一段例子来解释下上面说的理论: 41 | ````php 42 | 45 | 46 | ```` 47 | 48 | 上面是一段PHP语言的例子,我们创建了一个变量,并把它的值设置为'bar',在以后的代码中我们便可以使用$foo变量。相同的功能我们怎样在内核中实现呢?我们可以先构思一下步骤: 49 | 50 | * 创建一个zval结构,并设置其类型。 51 | * 设置值为'bar'。 52 | * 将其加入当前作用域的符号表,只有这样用户才能在PHP里使用这个变量。 53 | * 具体的代码为: 54 | 55 | ````c 56 | { 57 | zval *fooval; 58 | 59 | MAKE_STD_ZVAL(fooval); 60 | ZVAL_STRING(fooval, "bar", 1); 61 | ZEND_SET_SYMBOL( EG(active_symbol_table) , "foo" , fooval); 62 | } 63 | ```` 64 | 65 | 首先,我们声明一个zval指针,并申请一块内存。然后通过ZVAL_STRING宏将值设置为‘bar’,最后一行的作用就是将这个zval加入到当前的符号表里去,并将其label定义成foo,这样用户就可以在代码里通过$foo来使用它了。 66 | 67 | 68 | (php7) 69 | 70 | 我们在前两节已经了解了PHP中变量的类型和值是怎样在内核中用C语言实现的, 71 | 这一节我们将看一下内核是怎样来组织用户在PHP中定义的变量的。 72 | 73 | 有一点对我们扩展开发者来说非常棒,那就是用户在PHP中定义的变量我们都可以在一个HashTable中找到, 74 | 当PHP中定义了一个变量,内核会自动的把它的信息储存到一个用HashTable实现的符号表里。 75 | 76 | 全局作用域的符号表是在调用扩展的RINIT方法(一般都是MINIT方法里)前创建的,并在RSHUTDOWN方法执行后自动销毁。 77 | 78 | 当用户在PHP中调用一个函数或者类的方法时,内核会创建一个新的符号表并激活之, 79 | 这也就是为什么我们无法在函数中使用在函数外定义的变量的原因 80 | (因为它们分属两个符号表,一个当前作用域的,一个全局作用域的)。 81 | 如果不是在一个函数里,则全局作用域的符号表处于激活状态。 82 | 83 | 我们现在打开Zend/zend_globals.h文件,看一下_zend_execution_globals结构体,会在其中发现这么两个element: 84 | 85 | ````c 86 | struct _zend_executor_globals { 87 | ... 88 | zval *vm_stack_top; 89 | zval *vm_stack_end; 90 | zend_vm_stack vm_stack; 91 | 92 | struct _zend_execute_data *current_execute_data; 93 | zend_class_entry *scope; 94 | 95 | zend_long precision; 96 | ... 97 | }; 98 | 99 | struct _zend_execute_data { 100 | const zend_op *opline; /* executed opline */ 101 | zend_execute_data *call; /* current call */ 102 | zval *return_value; 103 | zend_function *func; /* executed funcrion */ 104 | zval This; /* this + call_info + num_args */ 105 | zend_class_entry *called_scope; 106 | zend_execute_data *prev_execute_data; 107 | zend_array *symbol_table; 108 | #if ZEND_EX_USE_RUN_TIME_CACHE 109 | void **run_time_cache; /* cache op_array->run_time_cache */ 110 | #endif 111 | #if ZEND_EX_USE_LITERALS 112 | zval *literals; /* cache op_array->literals */ 113 | #endif 114 | }; 115 | 116 | 117 | //info.c中例子如下 118 | PHPAPI void php_print_info(int flag) 119 | { 120 | ... 121 | php_print_gpcse_array(ZEND_STRL("_REQUEST")); 122 | php_print_gpcse_array(ZEND_STRL("_GET")); 123 | ... 124 | } 125 | 126 | static void php_print_gpcse_array(char *name, uint name_length) 127 | { 128 | zval *data, *tmp, tmp2; 129 | zend_string *string_key; 130 | zend_ulong num_key; 131 | zend_string *key; 132 | 133 | key = zend_string_init(name, name_length, 0); 134 | zend_is_auto_global(key); 135 | 136 | if ((data = zend_hash_find(&EG(symbol_table), key)) != NULL && (Z_TYPE_P(data) == IS_ARRAY)) { 137 | ... 138 | } 139 | 140 | /* Executor */ 141 | #ifdef ZTS 142 | # define EG(v) ZEND_TSRMG(executor_globals_id, zend_executor_globals *, v) 143 | #else 144 | # define EG(v) (executor_globals.v) 145 | extern ZEND_API zend_executor_globals executor_globals; 146 | #endif 147 | ```` 148 | 149 | 其中的 symbol_table元素可以通过EG宏来访问,它代表着PHP的全局变量可以通过EG(symbol_table)来访问, symbol_table由原来的HashTable修改为了现在的zend_array。 150 | 151 | 当前的符号表可以通过zend_rebuild_symbol_table()取得,原有的active_symbol_table已经删除了。 152 | 153 | 下面我们用一段例子来解释下上面说的理论: 154 | ````php 155 | 158 | 159 | ```` 160 | 161 | 上面是一段PHP语言的例子,我们创建了一个变量$foo,并把它的值设置为'bar',在以后的代码中我们便可以使用$foo变量。相同的功能我们怎样在内核中实现呢?我们可以先构思一下步骤: 162 | 163 | * 创建一个zval结构,并设置其类型。 164 | * 设置值为'bar'。 165 | * 将其加入当前作用域的符号表,只有这样用户才能在PHP里使用这个变量。 166 | * 具体的代码为: 167 | 168 | ````c 169 | { 170 | zval fooval; 171 | ZVAL_STRING(&fooval, "bar" ); 172 | zend_set_local_var_str("foo", sizeof("foo")-1, &fooval, 0); 173 | } 174 | ```` 175 | 176 | 首先,我们声明一个zval变量。然后通过ZVAL_STRING宏将值设置为"bar",最后一行的作用就是将这个zval加入到当前的符号表里去,并将其变量名定义成foo,这样用户就可以在代码里通过$foo来使用它了。 177 | 178 | 179 | ## links 180 | * [目录]() 181 | * 2.3 [创建PHP变量](<2.3.md>) 182 | * 2.5 [变量的检索](<2.5.md>) 183 | 184 | -------------------------------------------------------------------------------- /2.5.md: -------------------------------------------------------------------------------- 1 | # 2.5 变量的检索 2 | 3 | (php5) 4 | 5 | 用户在PHP语言里定义的变量,我们能否在内核中获取到呢? 6 | 答案当然是肯定的,下面我们就看如何通过zend_hash_find()函数来找到当前某个作用域下用户已经定义好的变量。 7 | zend_hash_find()函数是内核提供的操作HashTable的API之一,如果你没有接触过,可以先记住怎么使用就可以了。 8 | 9 | ````c 10 | { 11 | zval **fooval; 12 | 13 | if (zend_hash_find( 14 | &EG(active_symbol_table), //这个参数是地址,如果我们操作全局作用域,则需要&EG(symbol_table) 15 | "foo", 16 | sizeof("foo"), 17 | (void**)&fooval 18 | ) == SUCCESS 19 | ) 20 | { 21 | php_printf("成功发现$foo!"); 22 | } 23 | else 24 | { 25 | php_printf("当前作用域下无法发现$foo."); 26 | } 27 | } 28 | 29 | ```` 30 | 31 | 首先我们定义了一个指向指针的指针,然后通过zend_hash_find去EG(active_symbol_table)作用域下寻找名称为foo($foo)的变量, 32 | 如果成功找到,此函数将返回SUCCESS。看完代码,你肯定有很多疑问。 33 | 为什么还要进行`sizeof("foo")`运算,fooval明明是`zval**`型的,为什么转成`void**`的? 34 | 而且为什么还要进行&fooval运算,fooval本身不就已经是指向指针的指针了吗?:-), 35 | 该回答的问题确实很多,不要过于担心,让我们带着这些问题继续往下走。 36 | 37 | 首先要说明的是,内核定义HashTable这个结构,并不是单单用来储存PHP语言里的变量的, 38 | 其它很多地方都在应用HashTable**(这就是个神器)**。 39 | 一个HashTable有很多元素,在内核里叫做bucket。然而每个bucket的大小是固定的, 40 | 所以如果我们想在bucket里存储任意数据时,最好的办法便是申请一块内存保存数据, 41 | 然后在bucket里保存它的指针。以`zval *foo`为例, 42 | 内核会先申请一块足够保存指针内存来保存foo,比如这块内存的地址是p,也就是p=&foo, 43 | 并在bucket里保存p,这时我们便明白了,p其实就是`zval**`类型的。 44 | 至于bucket为什么保存`zval**`类型的指针,而不是直接保存`zval*`类型的指针,我们到下一章在详细叙述。 45 | 46 | 所以当我们去HashTable里寻找变量的时候,得到的值其实是一个zval的指针。 47 | In order to populate that pointer into a calling function's local storage, 48 | the calling function will naturally dereference the local pointer, 49 | resulting in a variable of indeterminate type with two levels of indirection (such as `void**`). 50 | Knowing that your "indeterminate type" in this case is `zval*`, 51 | you can see where the type being passed into zend_hash_find() will look different to the compiler, 52 | having three levels of indirection rather than two. 53 | This is done on purpose here so a simple typecast is added to the function call to silence compiler warnings. 54 | 55 | 如果zend_hash_find()函数找到了我们需要的数据,它将返回SUCCESS常量, 56 | 并把它的地址赋给我们在调用zend_hash_find()函数传递的fooval参数, 57 | 也就是说此时fooval就指向了我们要找的数据。如果没有找到,那它不会对我们fooval参数做任何修改,并返回FAILURE常量。 58 | 59 | 就去符号表里找变量而言,SUCCESS和FAILURE仅代表这个变量是否存在而已。 60 | 61 | 62 | (php7) 63 | 64 | 用户在PHP语言里定义的变量,我们能否在内核中获取到呢? 65 | 答案当然是肯定的,下面我们就看如何通过zend_hash_find()函数来找到当前某个作用域下用户已经定义好的变量。 66 | zend_hash_find()函数是内核提供的操作HashTable的API之一,如果你没有接触过,可以先记住怎么使用就可以了。 67 | 68 | ````c 69 | { 70 | //zval *fooval; 71 | 72 | if ( /* fooval = */ zend_hash_str_find( 73 | &EG(symbol_table), 74 | "foo", 75 | sizeof("foo") - 1 )) 76 | { 77 | php_printf("成功发现$foo!"); 78 | } 79 | else 80 | { 81 | php_printf("当前作用域下无法发现$foo."); 82 | } 83 | } 84 | 85 | ```` 86 | 87 | 首先我们定义了一个指向指针的指针,然后通过zend_hash_str_find去EG(symbol_table)作用域下寻找名称为foo($foo)的变量, 88 | 如果成功找到,此函数将返回zval变量的指针。 89 | 90 | 首先要说明的是,内核定义HashTable这个结构,并不是单单用来储存PHP语言里的变量的, 91 | 其它很多地方都在应用HashTable**(这就是个神器)**。 92 | 一个HashTable有很多元素,在内核里叫做bucket。然而每个bucket的大小是固定的, 93 | 所以如果我们想在bucket里存储任意数据时,最好的办法便是申请一块内存保存数据, 94 | 然后在bucket里保存它的指针。以`zval *foo`为例, 95 | 内核会先申请一块足够保存指针内存来保存foo,比如这块内存的地址是p,也就是p=&foo, 96 | 并在bucket里保存p,这时我们便明白了,p其实就是`zval**`类型的。 97 | 至于bucket为什么保存`zval**`类型的指针,而不是直接保存`zval*`类型的指针,我们到下一章在详细叙述。 98 | 99 | 所以当我们去HashTable里寻找变量的时候,得到的值其实是一个zval的指针。 100 | In order to populate that pointer into a calling function's local storage, 101 | the calling function will naturally dereference the local pointer, 102 | resulting in a variable of indeterminate type with two levels of indirection (such as `void**`). 103 | Knowing that your "indeterminate type" in this case is `zval*`, 104 | you can see where the type being passed into zend_hash_find() will look different to the compiler, 105 | having three levels of indirection rather than two. 106 | This is done on purpose here so a simple typecast is added to the function call to silence compiler warnings. 107 | 108 | 如果zend_hash_str_find()函数找到了我们需要的数据,它将返回指向该数据的指针,如果没有找到,并返回NULL。 109 | 110 | ## links 111 | * [目录]() 112 | * 2.4 [变量的存储方式](<2.4.md>) 113 | * 2.6 [类型转换](<2.6.md>) 114 | 115 | -------------------------------------------------------------------------------- /2.6.md: -------------------------------------------------------------------------------- 1 | # 2.6 类型转换 2 | 3 | (php5) 4 | 5 | 现在我们已经可以从符号表中获取用户在PHP语言里定义的变量了,是该做点其它事的时候了,举个例子,比如给它来个类型转换:-)。想想C语言中的类型转换细则,你的头是不是已经大了?但是变量的类型转换就是如此重要,如果没有,那我们的代码就会是下面这样了: 6 | 7 | ````c 8 | void display_zval(zval *value) 9 | { 10 | switch (Z_TYPE_P(value)) { 11 | case IS_NULL: 12 | /* 如果是NULL,则不输出任何东西 */ 13 | break; 14 | 15 | case IS_BOOL: 16 | /* 如果是bool类型,并且true,则输出1,否则什么也不干 */ 17 | if (Z_BVAL_P(value)) { 18 | php_printf("1"); 19 | } 20 | break; 21 | case IS_LONG: 22 | /* 如果是long整型,则输出数字形式 */ 23 | php_printf("%ld", Z_LVAL_P(value)); 24 | break; 25 | case IS_DOUBLE: 26 | /* 如果是double型,则输出浮点数 */ 27 | php_printf("%f", Z_DVAL_P(value)); 28 | break; 29 | case IS_STRING: 30 | /* 如果是string型,则二进制安全的输出这个字符串 */ 31 | PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value)); 32 | break; 33 | case IS_RESOURCE: 34 | /* 如果是资源,则输出Resource #10 格式的东东 */ 35 | php_printf("Resource #%ld", Z_RESVAL_P(value)); 36 | break; 37 | case IS_ARRAY: 38 | /* 如果是Array,则输出Array5个字母! */ 39 | php_printf("Array"); 40 | break; 41 | case IS_OBJECT: 42 | php_printf("Object"); 43 | break; 44 | default: 45 | /* Should never happen in practice, 46 | * but it's dangerous to make assumptions 47 | */ 48 | php_printf("Unknown"); 49 | break; 50 | } 51 | } 52 | 53 | ```` 54 | 看完上面的代码,你是不是有点似曾相识的感觉?o(∩∩)o...哈哈,和直接<?php echo $foo;?>这个简单到极点的php语句来比,上面的实现算是天书了。当然,真正的环境并没有这么囧,内核中提供了好多函数专门来帮我们实现类型转换的功能,你需要的只是调用一个函数而已。这一类函数有一个统一的形式:convert_to_*() 55 | ````c 56 | //将任意类型的zval转换成字符串 57 | void change_zval_to_string(zval *value) 58 | { 59 | convert_to_string(value); 60 | } 61 | 62 | //其它基本的类型转换函数 63 | ZEND_API void convert_to_long(zval *op); 64 | ZEND_API void convert_to_double(zval *op); 65 | ZEND_API void convert_to_null(zval *op); 66 | ZEND_API void convert_to_boolean(zval *op); 67 | ZEND_API void convert_to_array(zval *op); 68 | ZEND_API void convert_to_object(zval *op); 69 | 70 | ZEND_API void _convert_to_string(zval *op ZEND_FILE_LINE_DC); 71 | #define convert_to_string(op) if ((op)->type != IS_STRING) { _convert_to_string((op) ZEND_FILE_LINE_CC); } 72 | 73 | ```` 74 | 这里面有两个比较特殊,一个就是convert_to_string其实是一个宏函数,调用的另外一个函数;第二个便是没有convert_to_resource()的转换函数,因为资源的值在用户层面上,根本就没有意义,内核不会对它的值(不是指那个数字)进行转换。 75 | 76 | 好了,我们用php的echo的时候会先把变量转换成字符串,但是我们看见convert_to_string的参数是zval*的,你是不是开始担心在进行数据转换时破坏了原来数据的值?而我们`handle); 113 | break; 114 | case IS_ARRAY: 115 | /* 如果是Array,则输出Array5个字母! */ 116 | php_printf("Array"); 117 | break; 118 | case IS_OBJECT: 119 | php_printf("Object"); 120 | break; 121 | default: 122 | /* Should never happen in practice, 123 | * but it's dangerous to make assumptions 124 | */ 125 | php_printf("Unknown"); 126 | break; 127 | } 128 | } 129 | 130 | ```` 131 | 看完上面的代码,你是不是有点似曾相识的感觉?o(∩∩)o...哈哈,和直接<?php echo $foo;?>这个简单到极点的php语句来比,上面的实现算是天书了。当然,真正的环境并没有这么囧,内核中提供了好多函数专门来帮我们实现类型转换的功能,你需要的只是调用一个函数而已。这一类函数有一个统一的形式:convert_to_*() 132 | ````c 133 | //将任意类型的zval转换成字符串 134 | void change_zval_to_string(zval *value) 135 | { 136 | convert_to_string(value); 137 | } 138 | 139 | //其它基本的类型转换函数 140 | ZEND_API void convert_to_long(zval *op); 141 | ZEND_API void convert_to_double(zval *op); 142 | ZEND_API void convert_to_null(zval *op); 143 | ZEND_API void convert_to_boolean(zval *op); 144 | ZEND_API void convert_to_array(zval *op); 145 | ZEND_API void convert_to_object(zval *op); 146 | 147 | ZEND_API void _convert_to_string(zval *op ZEND_FILE_LINE_DC); 148 | #define convert_to_string(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_string((op) ZEND_FILE_LINE_CC); } 149 | 150 | ```` 151 | 这里面有两个比较特殊,一个就是convert_to_string其实是一个宏函数,调用的另外一个函数;第二个便是没有convert_to_resource()的转换函数,因为资源的值在用户层面上,根本就没有意义,内核不会对它的值(不是指那个数字)进行转换。 152 | 153 | 除了使用宏来从zval中取值外还可以使用如下函数取值 154 | 155 | ```c 156 | 157 | static zend_always_inline zend_long zval_get_long(zval *op) { 158 | return EXPECTED(Z_TYPE_P(op) == IS_LONG) ? Z_LVAL_P(op) : zval_get_long_func(op); 159 | } 160 | 161 | static zend_always_inline double zval_get_double(zval *op) { 162 | return EXPECTED(Z_TYPE_P(op) == IS_DOUBLE) ? Z_DVAL_P(op) : zval_get_double_func(op); 163 | } 164 | 165 | static zend_always_inline zend_string *zval_get_string(zval *op) { 166 | return EXPECTED(Z_TYPE_P(op) == IS_STRING) ? zend_string_copy(Z_STR_P(op)) : zval_get_string_func(op); 167 | } 168 | 169 | ``` 170 | 好了,我们用php的echo的时候会先把变量转换成字符串,但是我们看见convert_to_string的参数是zval*的,你是不是开始担心在进行数据转换时破坏了原来数据的值?而我们`) 174 | * 2.5 [变量的检索](<2.5.md>) 175 | * 2.7 [第二章小结](<2.7.md>) 176 | 177 | -------------------------------------------------------------------------------- /2.7.md: -------------------------------------------------------------------------------- 1 | # 2.7 小结 2 | 3 | 在这一章我们了解了php变量在内核中是如何实现的,我们已经可以识别出一个变量的类型,把它加到符号表去或者从符号表中找出等等等等。在下一章我们的目光开始转向内存,顺道研究下怎样复制已经存在的zval,以及如何在它们没用的时候及时的清理掉,还有最重要的,怎么不使用copy,而使用引用! 4 | 5 | 我们已经了解到zend引擎中针对一个请求的内存管理层,了解了常驻内存与非常驻内存的概念与区别。在读完下一章后,我们便有了比较完整的理论基础来在我们自己的扩展中灵活的操作各个变量。 6 | 7 | 8 | ## links 9 | * [目录]() 10 | * 2.6 [类型转换](<2.6.md>) 11 | * 3 [内存管理](<3.md>) 12 | 13 | -------------------------------------------------------------------------------- /2.md: -------------------------------------------------------------------------------- 1 | # 2 PHP变量在内核中的实现 2 | 3 | * [1. 变量的类型](2.1.md) 4 | * [2. 变量的值](2.2.md) 5 | * [3. 创建PHP变量](2.3.md) 6 | * [4. 变量的存储方式](2.4.md) 7 | * [5. 变量的检索](2.5.md) 8 | * [6. 类型转换](2.6.md) 9 | * [7. 小结](2.7.md) 10 | 11 | 所有的编程语言都要提供一种数据的存储与检索机制,PHP也不例外。其它语言大都需要在使用变量之前先定义,并且它的类型也是无法再次改变的,而PHP却允许程序猿自由的使用变量而无须提前定义,甚至可以随时随意的对已存在的变量转换成其它任何PHP支持的数据类型。在程序在运行的时候,PHP还会自动的根据需求转换变量的类型。 12 | 13 | 我认为阅读本书的人都已经是标准的PHP程序猿了,所以你们也肯定体验过PHP的弱类型的变量体系。众所周知,PHP引擎是用C写的,而C却是一种强类型的编程语言,PHP内核中是如何用C来实现自己的这种弱类型特性的,你将在本章中找到答案! 14 | 15 | 16 | ## links 17 | * [目录]() 18 | * 1.5 [小结](<1.5.md>) 19 | * 2.1 [变量的类型](<2.1.md>) 20 | 21 | -------------------------------------------------------------------------------- /20.1.md: -------------------------------------------------------------------------------- 1 | # 回调到php中 2 | 3 | 除了加载外部的脚本, 和你在上⼀章看到的类似, 你的php嵌入式应用, 下面将实现⼀个类似于用户空间eval()的命令. 4 | 5 | ````c 6 | int zend_eval_string(char *str, zval *retval_ptr, char *string_name TSRMLS_DC) 7 | ```` 8 | 9 | 这里, str是实际要执行的php脚本代码, 而string_name是⼀个与执行关联的任意描述信息. 如果发生错误, php会将这个描述信息作为错误输出中的"文件名". retval_ptr, 你应该 已经猜到了, 它将被设置为所传递代码产生的返回值. 试试用下面的代码创建新的项目吧. 10 | 11 | ````c 12 | #include int main(int argc, char *argv[]) { PHP_EMBED_START_BLOCK(argc, argv) zend_eval_string("echo 'Hello World!';", NULL, "Simple Hello World App" TSRMLS_CC); PHP_EMBED_END_BLOCK() return 0; 13 | } 14 | ```` 15 | 16 | 现在使用命令或第19章"设置宿主环境"构建它(将Makefile中或命令中的embed1替换为embed2) 17 | 18 | #### 备选方案: 脚本文件的包含 19 | 可以预见的是, 这使得编译和执行外部脚本文件远比之前的方法更加容易, 因为你的 应用可以将原本复杂的打开/准备/执行的执行序列, 以这种简化但功能更加强大的设计替代: 20 | 21 | ````c 22 | #include int main(int argc, char *argv[]) { char *filename; if ( argc <= 1 ) { fprintf(stderr, "Usage: %s \n", argv[1]); return -1; } filename = argv[1]; /* 忽略第0个参数 */ argc --; argv ++; PHP_EMBED_START_BLOCK(argc, argv) char *include_script; spprintf(&include_script, 0, "include '%s';", filename); zend_eval_string(include_script, NULL, filename TSRMLS_CC); efree(include_script); PHP_EMBED_END_BLOCK() return 0; } 23 | ```` 24 | 25 | 注意: 这种特殊的方法必须接受一个缺点, 如果文件名包含单引号, 将导致解析错误. 不过这可以通过使用ext/standard/php_string.h中的php_addslashes()API调用解决. 花一些时间去阅读这个 文件以及附录中的API参考, 你会发现很多的特性, 它们可以让你避免在以后重造轮子. 26 | 27 | #### 调用用户空间函数 28 | 29 | 如你看到的加载和执行脚本文件, 在内部有两种方式调用用户空间函数. 现在最明显 的可能是重用zend_eval_string(), 将函数名和所有它的参数组织到⼀个庞大的字符串中, 然后收集返回值. 30 | 31 | ````c 32 | PHP_EMBED_START_BLOCK(argc, argv) char *command; zval retval; spprintf(&command, 0, "nl2br('%s');", argv[1]); zend_eval_string(command, &retval, "nl2br() execution" TSRMLS_CC); efree(command); printf("out: %s\n", Z_STRVAL(retval)); zval_dtor(&retval); PHP_EMBED_END_BLOCK() 33 | ```` 34 | 和前面的include很像, 这个方法有⼀个致命的缺陷: 如果输入参数paramin(译者给出 的例子中是argv[1])给出⼀个错误的数据, 函数将会失败, 或者更糟糕的是导致无法预期的 结果. 解决方案是永远都避免编译代码的运行时片段, 并直接使用call_user_function()API调用函数. 35 | 36 | ````c 37 | int call_user_function(HashTable *function_table, zval **object_pp, zval *function_name, zval *retval_ptr, zend_uint param_count, zval *params[] TSRMLS_DC); 38 | ```` 39 | 40 | 实际上从引擎外部调用时, function_table总是EG(function_table). 如果调用⼀个对象或类方法, object_pp需要是IS_OBJECT类型的调用实例zval, 或者对于类的静态调用则是 IS_STRING的值. function_name通常是IS_STRING的值, 包含要调用的函数名, 但是它也 可以是IS_ARRAY, 第0个元素包含一个对象或类名, 第1个元素包含方法名. 这个函数调用的结果是向传入的retval_ptr指向的zval设置返回值. param_count和 params扮演了argc/argv的角色. 也就是说, params[0]包含所传递的第一个参数, params[param_count - 1]包含了所传递的最后一个参数. 下面是用这种方法重新实现上面的例子: 41 | ````c PHP_EMBED_START_BLOCK(argc, argv) char *command; zval retval; spprintf(&command, 0, "nl2br('%s');", argv[1]); zend_eval_string(command, &retval, "nl2br() execution" TSRMLS_CC); efree(command); printf("out: %s\n", Z_STRVAL(retval)); zval_dtor(&retval); PHP_EMBED_END_BLOCK() int call_user_function(HashTable *function_table, zval **object_pp, zval *function_name, zval *retval_ptr, zend_uint param_count, zval *params[] TSRMLS_DC); PHP_EMBED_START_BLOCK(argc, argv) zval *args[1]; zval retval, str, funcname; ZVAL_STRING(&funcname, "nl2br", 0); args[0] = &str; ZVAL_STRINGL(args[0], "HELLO WORLD!", sizeof("HELLO WORLD!"), 1); call_user_function(EG(function_table), NULL, &funcname, &retval, 1, args TSRMLS_CC); printf("out: %s\n", Z_STRVAL(retval)); zval_dtor(args[0]); zval_dtor(&retval); PHP_EMBED_END_BLOCK() ```` 42 | 尽管代码看起来比较长, 但是工作量会显著降低, 因为这里没有要编译的中间代码, 传 递的数据不需要复制, 每个参数都已经在Zend兼容的结构体中. 同时, 要记得原来的例子中 在字符串中包含单引号时会有潜在的错误. 而这个版本没有这个问题. 43 | 44 | ## links 45 | * [目录]() 46 | * 20 [高级嵌入式](<20.md>) 47 | * 20.2 [错误处理](<20.2.md>) 48 | -------------------------------------------------------------------------------- /20.2.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | 当发生错误时, 比如脚本解析错误, php将会进入到bailout模式. 在你已经看到的简单 的嵌入式例子中, 这表示它将直接跳到PHP_EMBED_END_BLOCK()宏, 并且绕过所有这个块中的剩余代码. 由于多数潜入php解释器的应用, 目的并不只是为了执行php代码, 因 此避免由于php脚本的故障导致整个应用崩溃是有意义的. 4 | 5 | 有⼀种方式可以将所有的执行限制到一个非常小的START/END块中, 这样发生崩溃 就只影响当前块. 这种方式的缺点是每个START/END块函数都是独立的PHP请求. 因此比 如下面START/END块, 虽然从语法逻辑上来看两个块是协同工作的, 但实际上它们之间是不共享公共作用域的. 6 | 7 | ````c 8 | int main(int argc, char *argv[]) 9 | { 10 | PHP_EMBED_START_BLOCK(argc, argv) 11 | zend_eval_string("$a = 1;", NULL, "Script Block 1"); 12 | PHP_EMBED_END_BLOCK() 13 | PHP_EMBED_START_BLOCK(argc, argv) 14 | /* 将打印出"NULL", 因为变量$a在这个请求中并没有定义. */ 15 | zend_eval_string("var_dump($a);", NULL, "Script Block 2"); 16 | PHP_EMBED_END_BLOCK() 17 | return 0; 18 | } 19 | ```` 20 | 21 | 还有一种解决方法是将两个zend_eval_string()调用使用Zend特有的伪语言结构 zend_try, zend_catch, zend_end_try进行隔离. 使用这些结构, 你的应用就可以按照想要的方式处理错误. 考虑下面的代码: 22 | 23 | ````c 24 | int main(int argc, char *argv[]) 25 | { 26 | PHP_EMBED_START_BLOCK(argc, argv) 27 | zend_try { 28 | /* 尝试执行⼀一些可能失败的代码 */ 29 | zend_eval_string("$1a = 1;", NULL, "Script Block 1a"); 30 | } zend_catch { 31 | /* 发生错误, 则尝试执行另外⼀一部分代码(⼀一般错误的补救或报告等行为) */ 32 | zend_eval_string("$a = 1;", NULL, "Script Block 1"); 33 | } zend_end_try(); 34 | /* 这里将显示"NULL", 因为变量$a在这个请求中没有定义. */ zend_eval_string("var_dump($a);", NULL, "Script Block 2"); 35 | PHP_EMBED_END_BLOCK() 36 | return 0; } 37 | ```` 38 | 39 | 在这个示例的第二个版本中, zend_try块中将发生解析错误, 但它只影响自己的代码 块, 同时在zend_catch块中使用了⼀段好的代码对错误进行了处理. 同样你也可以尝试自 己给var_dump()部分也加上这些块. 40 | 41 | ````c 42 | 译注: 这里对zend_try/zend_catch/zend_end_try解释的不是很清楚, 因此做以下补充说明. 读 者阅读这一部分内容需要首先了解sigsetjmp()/siglongjmp()的机制(可以参考 第10章第15节). 43 | 相关的定义如下: 44 | #ifdef HAVE_SIGSETJMP# define SETJMP(a) sigsetjmp(a, 0) 45 | # define LONGJMP(a,b) siglongjmp(a, b) 46 | # define JMP_BUF sigjmp_buf 47 | #else 48 | # define SETJMP(a) setjmp(a) 49 | # define LONGJMP(a,b) longjmp(a, b) 50 | # define JMP_BUF jmp_buf 51 | #endif 52 | #define zend_try \ 53 | { \ 54 | JMP_BUF *__orig_bailout = EG(bailout); \ 55 | JMP_BUF __bailout; \ 56 | \ 57 | EG(bailout) = &__bailout; \ 58 | if (SETJMP(__bailout)==0) { 59 | #define zend_catch \ 60 | } else { \ 61 | EG(bailout) = __orig_bailout; 62 | #define zend_end_try() \ }\ EG(bailout) = __orig_bailout; \ 63 | } 64 | ```` 65 | 66 | zend_try{}代码块中的代码是在一个if语句中的, 这个if的条件是SETJMP(__bailout) == 0, SETJMP()是在当前程序执行的点设置一个可回溯的点(保存了当前执行上下文和环境), SETJMP() 的返回比较特殊, 它有两种返回: 1) 直接返回, 此时返回值为0; 2) 调用LONGJMP()返回到对应 __bailout当时调用SETJMP()的位置, 此时返回值非0. 67 | 68 | 基于上面的论述, 可以看出, 当zend_try的代码块中调用了LONGJMP()的时候, 程序将回到if ( SETJMP(__bailout) == 0 )的位置开始执行, 并且它的返回值为-1, 因此, 进入到对应的else语句 块, 也就是zend_catch语句块的代码. 69 | 70 | zend_end_try()则只是⼀个结尾的花括号. 71 | 72 | php中的这个伪语言结构正式这种方式实现的异常处理机制, 在系统的关键点调用 zend_bailout()(在Zend/zend.h中定义)即可. 73 | 74 | 本例中, 译者增加了zend_bailout()调用, 演示了这个伪语言结构的使用. 75 | 76 | ## links 77 | * [目录]() 78 | * 20.1 [回调到php中](<20.1.md>) 79 | * 20.3 [初始化php](<20.3.md>) 80 | -------------------------------------------------------------------------------- /20.3.md: -------------------------------------------------------------------------------- 1 | # 初始化php 2 | 3 | 迄今为止, 你看到的PHP_EMBED_START_BLOCK()和 PHP_EMBED_END_BLOCK()宏都用于启动, 执行, 终止一个紧凑的原子的php请求。 4 | 这样 做的优点是任何导致php bailout的错误顶多影响到PHP_EMBED_END_BLOCK()宏之内 的当前作用域. 通过将你的代码执行放入到这两个宏之间的小块中, php的错误就不会影响到你的整个应用. 5 | 你刚才已经看到了, 这种短小精悍的方法主要的缺点在于每次你建立一个新的 START/END块的时候, 都需要创建⼀个新的请求, 新的符号表, 因此就失去了所有的持久性语义. 6 | 要想同时得到两种优点(持久化和错误处理), 就需要将START和END宏分解为它们各 自的组件(译注: 如果不明白可以参考这两个宏的定义). 下面是本章开始给出的embed2.c 程序, 这⼀次, 我们对它进行了分解: 7 | ````c 8 | #include 9 | int main(int argc, char *argv[]) 10 | { 11 | #ifdef ZTS 12 | void ***tsrm_ls; 13 | #endif 14 | 15 | php_embed_init(argc, argv PTSRMLS_CC); 16 | zend_first_try { 17 | zend_eval_string("echo 'Hello World!';", NULL,"Embed 2 Eval'd string" TSRMLS_CC); 18 | } zend_end_try(); 19 | php_embed_shutdown(TSRMLS_C); 20 | return 0; 21 | } 22 | ```` 23 | 24 | 25 | 它执行和之前⼀样的代码, 只是这一次你可以看到打开和关闭的括号包裹了你的代码, 而不是无法分开的START和END块。 26 | 将php_embed_init()放到你应用的开始, 将php_embed_shutdown()放到末尾, 你的应用就得到了一个持久的单请求生命周期, 它还可 以使用zend_first_try {} zend_end_try(); 结构捕获所有可能导致你整个包装应用跳出末尾 27 | 的PHP_EMBED_END_BLOCK()宏的致命错误. 28 | 为了看看真实世界环境的这种方法的应用, 我们将本章前面⼀些的例子的启动和终止 处理进行了抽象: 29 | 30 | ````c 31 | #include 32 | #ifdef ZTS 33 | void ***tsrm_ls; 34 | #endif 35 | static void startup_php(void) 36 | { 37 | /* Create "dummy" argc/argv to hide the arguments 38 | * meant for our actual application */ 39 | int argc = 1; 40 | char *argv[2] = { "embed4", NULL }; 41 | php_embed_init(argc, argv PTSRMLS_CC); 42 | } 43 | 44 | static void shutdown_php(void) 45 | { 46 | php_embed_shutdown(TSRMLS_C); 47 | } 48 | 49 | static void execute_php(char *filename) { 50 | 51 | zend_first_try { 52 | char *include_script; 53 | spprintf(&include_script, 0, "include '%s';", filename); 54 | zend_eval_string(include_script, NULL, filename TSRMLS_CC); 55 | efree(include_script); 56 | } zend_end_try(); 57 | } 58 | 59 | int main(int argc, char *argv[]) 60 | { 61 | if (argc <= 1) { 62 | printf("Usage: embed4 scriptfile"); 63 | return -1; 64 | } 65 | startup_php(); 66 | execute_php(argv[1]); 67 | shutdown_php(); 68 | return 0; 69 | } 70 | ```` 71 | 72 | 类似的概念也可以应用到处理任意代码的执行以及其他任务. 只需要确认在最外部的 容器上使用zend_first_try, 则里面的每个容器上使用zend_try即可. 73 | ## links 74 | * [目录]() 75 | * 20.2 [错误处理](<20.2.md>) 76 | * 20.4 [覆写INI_SYSTEM和INI_PERDIR选项](<20.4.md>) 77 | -------------------------------------------------------------------------------- /20.4.md: -------------------------------------------------------------------------------- 1 | # 覆写INI_SYSTEM和INI_PERDIR选项 2 | 3 | 在上一章中, 你曾经使用zend_alter_ini_setting()修改过⼀些php的ini选项. 由于samp/embed直接将你的脚本推入了运行时模式, 因此许多重要的INI选项在控制返回到你的应用 时并没有被修改. 为了修改这些值, 就需要在主引擎启动之后而请求启动之前执行代码. 有一种方式是拷贝php_embed_init()的内容到你的应用中, 在你的本地拷贝中做必要 的修改, 接着使用你修改后的版本替代它. 当然这种方式可能会有问题. 4 | 首先也是最重要的, 你实际已经对别人的部分代码做了分支, 然而可能别人还会向其 中添加新的代码. 现在, 你就不再是只维护自己的应用了, 还需要保持分支出来的代码和主 分支保持一致. 幸运的是, 还有几种更简单的方法: 5 | #### 覆写默认的php.ini文件 6 | 因为嵌入式和其他的php sapi实现一样都是sapi, 它通过⼀个sapi_module_struct挂入 到引擎中. 嵌入式SAPI定义并设置了这个结构体的一个实例, 你的应用可以在调用 php_embed_init()之前访问它. 7 | 在这个结构体中, 有一个名为php_ini_path_override的char *类型字段. 为了让嵌入的 请求使用你的可选文件扩展php和Zend, 只需要在调用php_embed_init()之前将这个字段 设置为NULL终止的字符串. 下面是embed4.c中修改版的startup_php()函数: 8 | ````c 9 | static void startup_php(void) { /* Create "dummy" argc/argv to hide the arguments * meant for our actual application */ int argc = 1; char *argv[2] = { "embed4", NULL }; php_embed_module.php_ini_path_override = "/etc/php_embed4.ini"; php_embed_init(argc, argv PTSRMLS_CC); } ```` 10 | 这就使得每个使用嵌入库的应用可以保持自定义, 而不用将自己的配置暴露给别人. 相反, 如果你想要你的应用不使用php.ini, 只需要设置php_embed_module的 php_ini_ignore字段, 这样所有的设置都将使用内建的默认值, 除非由你的应用手动进行修改. 11 | #### 覆写嵌入启动 12 | sapi_module_struct结构还包含⼀些回调函数, 下面是其中4个在PHP启动和终止阶段 比较有用的回调: 13 | ````c 14 | /* From main/SAPI.h */ typedef struct _sapi_module_struct { ... int (*startup)(struct _sapi_module_struct *sapi_module); int (*shutdown)(struct _sapi_module_struct *sapi_module); int (*activate)(TSRMLS_D); int (*deactivate)(TSRMLS_D); ... } sapi_module_struct; ```` 15 | 这些方法的名字熟悉吗? 它们对应于扩展的MINIT, MSHUTDOWN, RINIT, RSHUTDOWN, 并且和对应在扩展生命周期中的阶段⼀致. 要利用这些钩子, 可以如下修 改embed4中的startup_php()函数: 16 | 17 | ````c 18 | static int (*original_embed_startup)(struct _sapi_module_struct *sapi_module); static int embed4_startup_callback(struct _sapi_module_struct *sapi_module) { /* 首先调用原来的启动回调, 否则环境未就绪 */ if (original_embed_startup(sapi_module) == FAILURE) { /* 这里可以做应用的失败处理 */ return FAILURE; } /* 调用原来的embed_startup实际上让我们进入到ACTIVATE阶段而不是STARTUP阶段, * 但是我们仍然可以修改多数INI_SYSTEM和INI_PERDIR选项. */ zend_alter_ini_entry("max_execution_time", sizeof("max_execution_time"), "15", sizeof("15") - 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); zend_alter_ini_entry("safe_mode", sizeof("safe_mode"), "1", sizeof("1") - 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); return SUCCESS; } 19 | static void startup_php(void) { /* 创建假的argc/argv, 隐藏应用实际的参数 */ int argc = 1; char *argv[2] = { "embed4", NULL }; /* 使用我们自己的启动函数覆写标准的启动方法, 但是保留了原来的指针, 因此它仍然能够被调用到 */ original_embed_startup = php_embed_module.startup; php_embed_module.startup = embed4_startup_callback; php_embed_init(argc, argv PTSRMLS_CC); } 20 | ```` 21 | 22 | 使用safe_mode, open_basedir这样的选项, 以及其他用以限制独立脚本行为的选项, 可以让你的应用更加安全可靠. 23 | ## links 24 | * [目录]() 25 | * 20.3 [初始化php](<20.3.md>) 26 | * 20.5 [捕获输出](<20.5.md>) 27 | -------------------------------------------------------------------------------- /20.5.md: -------------------------------------------------------------------------------- 1 | # 捕获输出 2 | 3 | 除非你开发的是非常简单的控制台应用, 否则你应该不希望php脚本代码产生的输出 直接被扔到激活的终端上. 捕获这些输出和你刚才用以覆写启动处理器的方法类似. 4 | 在sapi_module_struct中还有⼀些有用的回调: 5 | ````c 6 | typedef struct _sapi_module_struct { ... int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC); void (*flush)(void *server_context); void (*sapi_error)(int type, const char *error_msg, ...); void (*log_message)(char *message); ... } sapi_module_struct; ```` 7 | #### 标准输出: ub_write 8 | 所有用户空间的echo和print语句产生的输出, 以及其他内部通过php_printf()或 PHPWRITE()产生的输出, 最终都将被发送到激活的SAPI的ub_write()方法. 默认情况, 嵌入式SAPI直接将这些数据交给stdout管道, 而不关心你的应用的输出策略. 9 | 假设你的应用想要把所有的输出都发送到⼀个独立的控制台窗口; 你可能需要实现⼀个类似于下面伪代码块所描述的回调: 10 | ````c 11 | static int embed4_ub_write(const char *str, unsigned int str_length TSRMLS_DC) { output_string_to_window(CONSOLE_WINDOW_ID, str, str_length); return str_length; } ```` 12 | 要让这个函数能够处理php产生的内容, 你需要在调用php_embed_init()之前对 php_embed_module结构做适当的修改: 13 | ````c 14 | php_embed_module.ub_write = embed4_ub_write; ```` 15 | 注意: 哪怕你决定你的应用不需要php产生的输出, 也必须为ub_write设置⼀个回调. 将它的值设置为NULL将导致引擎崩溃, 当然, 你的应用也不能幸免. 16 | #### 缓冲输出: Flush 17 | 你的应用可能会使用缓冲php产生的输出进行优化, sapi层提供了⼀个回调用以通知 你的应用"现在请发送你的缓冲区数据", 你的应用并没有义务去实施这个通知; 不过, 由于 这个信息通常是由于足够的理由(比如到达请求结束位置)才产生的, 听从这个意见并不会有什么坏处. 18 | 下面的这对回调函数, 以256字节缓冲区缓冲数据由引擎安排执行flush. 19 | ````c 20 | char buffer[256]; int buffer_pos = 0; static int embed4_ubwrite(const char *str, unsigned int str_length TSRMLS_DC) 21 | { char *s = str; char *d = buffer + buffer_pos; int consumed = 0; /* 缓冲区够用, 直接追加到缓冲区后面 */ if (str_length < (256 - buffer_pos)) { memcpy(d, s, str_length); buffer_pos += str_length; return str_length; } consumed = 256 - buffer_pos; memcpy(d, s, consumed); embed4_output_chunk(buffer, 256); str_length -= consumed; s += consumed; /* 消耗整个传入的块 */ while (str_length >= 256) { embed4_output_chunk(s, 256); s += 256; consumed += 256; } /* 重置缓冲区头指针内容 */ memcpy(buffer, s, str_length); buffer_pos = str_length; consumed += str_length; return consumed; } static void embed4_flush(void *server_context) { if (buffer_pos < 0) { /* 输出缓冲区中剩下的内容 */ embed4_output_chunk(buffer, buffer_pos); buffer_pos = 0; } 22 | } ```` 在startup_php()中增加下面的代码, 这个基础的缓冲机制就就绪了: 23 | ````c 24 | php_embed_module.ub_write = embed4_ub_write; 25 | php_embed_module.flush = embed4_flush; ```` 26 | #### 标准错误: log_message 27 | 在启用了log_errors INI设置时, 在启动或执行脚本时如果碰到错误, 将激活 log_message回调. 默认的php错误处理程序会在处理显示(这里是调用log_message回调)之前, 格式化这些错误消息, 使其称为整齐的, 人类可读的内容. 28 | 关于log_message回调, 这里你需要注意的第⼀件事是它并不包含长度参数, 因此它并不是二进制安全的. 也就是说, 它只是按照NULL终止来处理字符串末尾. 29 | 使用它来做错误报告通常不会有什么问题, 实际上, 它可以用于在错误消息的呈现上 做更多的事情. 默认情况下, sapi/embed将会通过这个简单的内建回调, 发送这些错误消息到标准错误管道: 30 | ````c 31 | static void php_embed_log_message(char *message) { fprintf (stderr, "%s\n", message); } ```` 32 | 如果你想发送这些消息到日志文件, 则可以使用下面的版本替代: 33 | ````c static void embed4_log_message(char *message) { FILE *log; log = fopen("/var/log/embed4.log", "a"); fprintf (log, "%s\n", message); fclose(log); } ```` 34 | #### 特殊错误: sapi_error 35 | 少数特殊情况的错误属于某个sapi, 因此将绕过php的主错误处理程序. 这些错误一般 是由于使用不当造成的, 比如非web应用不应该使用header()函数, 上传文件到控制台应用程序等. 36 | 由于这些情况都离你所开发的sapi/embed应用非常遥远, 因此最好保持这个回调为空. 不过, 如果你非要坚持去捕获每种类型错误的源, 也只需要实现⼀个回调函数, 并在调 用php_embed_init()之前覆写它就可以了. 37 | 38 | ## links 39 | * [目录]() 40 | * 20.4 [覆写INI_SYSTEM和INI_PERDIR选项](<20.4.md>) 41 | * 20.6 [同时扩展和嵌入](<20.6.md>) 42 | -------------------------------------------------------------------------------- /20.6.md: -------------------------------------------------------------------------------- 1 | # 同时扩展和嵌入 2 | 3 | 在你的应用中运行php代码固然不错, 但是此刻, php执行环境仍然和你的主应用是隔离的, 它们并没有在真正意义上的一个层级进行交互. 4 | 现在你应该对php扩展的开发以及构建启用方面比较熟悉了. 你也已经有完成了嵌入 工作的例程, 这样就省去了这份工作. 将扩展代码植入到嵌入式应用中的工作量要比标准扩展小. 下面是⼀个新的嵌入式项目: 5 | ````c 6 | #include #ifdef ZTS void ***tsrm_ls; #endif /* Extension bits */ zend_module_entry php_mymod_module_entry = { STANDARD_MODULE_HEADER, "mymod", /* extension name */ NULL, /* function entries */ NULL, /* MINIT */ NULL, /* MSHUTDOWN */ NULL, /* RINIT */ NULL, /* RSHUTDOWN */ NULL, /* MINFO */ "1.0", /* version */ STANDARD_MODULE_PROPERTIES }; /* Embedded bits */ static void startup_php(void) { int argc = 1; char *argv[2] = { "embed5", NULL }; php_embed_init(argc, argv PTSRMLS_CC); zend_startup_module(&php_mymod_module_entry); } static void execute_php(char *filename) { zend_first_try { char *include_script; spprintf(&include_script, 0, "include '%s'", filename); zend_eval_string(include_script, NULL, filename TSRMLS_CC); efree(include_script); } zend_end_try(); ] int main(int argc, char *argv[]) { if (argc <= 1) { printf("Usage: embed4 scriptfile";); return -1; } startup_php(); execute_php(argv[1]); php_embed_shutdown(TSRMLS_CC); 7 |  return 0; 8 | } ```` 9 | 现在, 你可以定义function_entry向量, 启动/终止函数, 定义类, 以及所有你想增加的东 西. 现在, 它和你使用用户空间的dl()命令加载这个扩展库一样, 在这⼀个命令中Zend将自 动的处理所有的钩子并对你的模块进行注册, 就绪等待使用.(译注: startup_php()中调用 zend_startup_module(&php_mymod_module_entry)进行了模块注册) 10 | 11 | ## links 12 | * [目录]() 13 | * 20.5 [捕获输出](<20.5.md>) 14 | * 20.7 [小结](<20.7.md>) 15 | -------------------------------------------------------------------------------- /20.7.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 本章你看了一些上一章的⼀些简单的嵌入式示例进行了扩展, 你已经可以将php放入 到各种非线程应用了. 现在你已经掌握了扩展和嵌入式的基础, 并且可以在zval, 类, 资源, HashTable上工作了, 你已经可以真正开始⼀个真正的项目了. 4 | 在剩下的附录中, 你将看到php, zend以及其他扩展暴露的很多API函数. 你将会看到一些常用的代码片段以及近几年数以百计的开源PECL项目, 它们都可以作为你未来项目 的参考. 5 | 6 | 7 | ## links 8 | * [目录]() 9 | * 20.6 [同时扩展和嵌入](<20.6.md>) 10 | -------------------------------------------------------------------------------- /20.md: -------------------------------------------------------------------------------- 1 | # 20 高级嵌入式 2 | 3 | ##目录 4 | 5 | * [1. 回调到php中](20.1.md) 6 | * [2. 错误处理](20.2.md) 7 | * [3. 初始化php](20.3.md) 8 | * [4. 覆写INI_SYSTEM和INI_PERDIR选项](20.4.md) 9 | * [5. 捕获输出](20.5.md) 10 | * [6. 同时扩展和嵌入](20.6.md) 11 | * [7. 小结](20.7.md) 12 | 13 | 14 | php的嵌入式能够提供的可不仅仅是同步的加载和执行脚本. 通过理解php的执行模块 各个部分是怎样组合的, 甚至给出一个脚本还可以回调到你的宿主应用中. 本章将涉及 SAPI层提供的I/O钩子带来的好处, 展开你已经从前面的主题中获取到信息的执行模块进行学习. 15 | 16 | 17 | ## links 18 | * [目录]() 19 | * 19.5 [小结](<19.5.md>) 20 | * 20.1 [回调到php中](<20.1.md>) 21 | -------------------------------------------------------------------------------- /3.2.md: -------------------------------------------------------------------------------- 1 | # 3.2 引用计数 2 | (TBD) 3 | 对于PHP这种需要同时处理多个请求的程序来说,申请和释放内存的时候应该慎之又慎,一不小心便会酿成大错。另一方面,除了要安全的申请和释放内存外,还应该做到内存的最小化使用,因为它可能要处理每秒钟数以千计的请求,为了提高系统整体的性能,每一次操作都应该只使用最少的内存,对于不必要的相同数据的复制则应该能免则免。我们来看下面这段PHP代码: 4 | ````php 5 | refcount < 2) 62 | { 63 | //如果这个变量的zval部分的refcount小于2,代表没有别的变量在用,return 64 | return *varval; 65 | } 66 | 67 | /* 否则,复制一份zval*的值 */ 68 | MAKE_STD_ZVAL(varcopy); 69 | varcopy = *varval; 70 | 71 | /* 复制任何在zval*内已分配的结构*/ 72 | zval_copy_ctor(varcopy); 73 | 74 | /* 从符号表中删除原来的变量 75 | * 这将减少该过程中varval的refcount的值 76 | */ 77 | zend_hash_del(EG(active_symbol_table), varname, varname_len + 1); 78 | 79 | /* 初始化新的zval的refcount,并在符号表中重新添加此变量信息,并将其值与我们的新zval相关联。*/ 80 | varcopy->refcount = 1; 81 | varcopy->is_ref = 0; 82 | zend_hash_add(EG(active_symbol_table), varname, varname_len + 1,&varcopy, sizeof(zval*), NULL); 83 | 84 | /* 返回新zval的地址 */ 85 | return varcopy; 86 | } 87 | 88 | ```` 89 | 现在$b变量拥有了自己的zval,并且可以自由的修改它的值了。 90 | ### Change on Write 91 | 如果用户在PHP脚本中显式的让一个变量引用另一个变量时,我们的内核是如何处理的呢? 92 |
 93 | 	$a = 1;
 94 | 	$b = &$a;
 95 | 	$b += 5;    	
 96 |     	
97 | 作为一个标准的PHP程序猿,我们都知道$a的值也变成6了。当我们更改$b的值时,内核发现$b是$a的一个用户端引用,也就是所它可以直接改变$b对应的zval的值,而无需再为它生成一个新的不同与$a的zval。因为他知道$a和$b都想得到这次变化! 98 | 但是内核是怎么知道这一切的呢?简单的讲,它是通过zval的is_ref__gc成员来获取这些信息的。这个成员只有两个值,就像开关的开与关一样。它的这两个状态代表着它是否是一个用户在PHP语言中定义的引用。在第一条语句($a = 1;)执行完毕后,$a对应的zval的refcount__gc等于1,is_ref__gc等于0;。 当第二条语句执行后($b = &$a;),refcount__gc属性向往常一样增长为2,而且is_ref__gc属性也同时变为了1! 99 | 最后,在执行第三条语句的时候,内核再次检查$b的zval以确定是否需要复制出一份新的zval结构来,这次不需要复制,因为我们刚才上面的get_var_and_separate函数其实是个简化版,并且少写了一个条件: 100 | ````c 101 | /* 如果这个zval在php语言中是通过引用的形式存在的,或者它的refcount小于2,则不需要复制。*/ 102 | if ((*varval)->is_ref || (*varval)->refcount < 2) { 103 | return *varval; 104 | } 105 | 106 | ```` 107 | 这一次,尽管它的refcount等于2,但是因为它的is_ref等于1,所以也不会被复制。内核会直接的修改这个zval的值。 108 | ### Separation Anxiety 109 | 我们已经了解了php语言中变量的复制和引用的一些事,但是如果复制和引用这两个事件被组合起来使用了该怎么办呢?看下面这段代码: 110 | ````php 111 | $a = 1; 112 | $b = $a; 113 | $c = &$a; 114 | 115 | ```` 116 | 这里我们可以看到,$a,$b,$c这三个变量现在共用一个zval结构,有两个属于change-on-write组合($a,$c),有两个属于copy-on-write组合($a,$b),我们的is_ref__gc和refcount__gc该怎样工作,才能正确的处理好这段复杂的关系呢? 117 | The answer is: 不可能!在这种情况下,变量的值必须分离成两份完全独立的存在!$a与$c共用一个zval,$b自己用一个zval,尽管他们拥有同样的值,但是必须至少通过两个zval来实现。见图3.2【在引用时强制复制!】 118 |

119 | 同样,下面的这段代码同样会在内核中产生歧义,所以需要强制复制! 120 |

121 | ````php 122 | //上图对应的代码 123 | $a = 1; 124 | $b = &$a; 125 | $c = $a; 126 | 127 | ```` 128 | 需要注意的是,在这两种情况下,$b都与原初的zval相关联,因为当复制发生时,内核还不知道第三个变量的名字。 129 | 130 | 131 | ## links 132 | * 3.1 [内存管理](<3.1.md>) 133 | * 3.3 [3.3 第三章总结](<3.3.md>) 134 | 135 | -------------------------------------------------------------------------------- /3.3.md: -------------------------------------------------------------------------------- 1 | # 3.3 内存管理 2 | 3 | PHP是一种解释型的语言,对于用户而言,我们精心的控制内存意味着easier prototyping和更少的崩溃!当我们深入到内核之后,所有的安全防线都已经被越过,最终还是要依赖于真正有责任心的软件工程师来保证系统的稳定运行。 4 | 5 | 6 | 7 | ## links 8 | * 3.2 [引用计数](<3.2.md>) 9 | * 4 [动手编译PHP](<4.md>) 10 | 11 | -------------------------------------------------------------------------------- /3.md: -------------------------------------------------------------------------------- 1 | # 3 内存管理 2 | 3 | * [1. 内存管理](3.1.md) 4 | * [2. 引用计数](3.2.md) 5 | * [3. 总结](3.3.md) 6 | 7 | 脚本语言与编译型语言最根本的区别可能就在内存管理上。但这并不限于脚本语言,现在越来越多的语言不再允许用户直接操作内存,而由虚拟机来代替用户负责内存的分配及回收,如C#、Java、PHP等。 8 | 9 | 10 | ## links 11 | * 2.7 [第二章小结](<2.7.md>) 12 | * 3.1 [内存管理](<3.1.md>) 13 | 14 | -------------------------------------------------------------------------------- /4.1.md: -------------------------------------------------------------------------------- 1 | # 4.1 动手编译PHP 2 | 3 | 从一个PHP程序猿,到一个想为PHP开发扩展的程序猿,此间的进化有一步是跳不过去的,那就是你必须熟知如何编译PHP的源码。 4 | ### *nix Tools 5 | C语言的编译器是我们使用C语言的必备工具,你的系统应该已经自带了一种C语言的编译器,而且它极有可能是大名鼎鼎的GCC。通过检测你本机gcc或者cc程序的版本,可以很方便的知道你机器上是否已经安装的某种C语言的编译器。 6 | ````shell 7 | walu@walu-ThinkPad-Edge:~$ gcc --version 8 | gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2 9 | Copyright (C) 2010 Free Software Foundation, Inc. 10 | This is free software; see the source for copying conditions. There is NO 11 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | 13 | 14 | ```` 15 | 如果你还没有安装编译器,那你需要安装一个。最简单的办法便是去下载一个与你系统相符的rpm或者deb包,当然你也可以通过以下命令的一种来方便的安装:yum install gcc, apt-get install gcc, pkg-add -r gcc, 或者 emerge gcc. 16 | 除了编译器,你还需要以下程序:make, autoconf, automake, 和libtool。说实话,我连autoconf现在是啥还不知道(截至到现在,2011年9月6号),不过除非RP太低,一般系统中都会自备了,而且phpize程序会把这些需要的脚本给生成好的。 17 | 对于编译需要的程序以及它们的版本我们可以在PHP官网找到最新的答案: 18 |

    19 |
  • autoconf: 2.13 (2.59+ for PHP 5.4+)
  • 20 |
  • automake: 1.4+
  • 21 |
  • libtool: 1.4.x+ (except 1.4.2)
  • 22 |
  • bison: 1.28, 1.35, 1.75, 2.0 or higher
  • 23 |
  • flex (PHP 5.2 and earlier): 2.5.4 (not higher)
  • 24 |
  • re2c: 0.13.4+
  • 25 |
26 | 你千万不要被上面的清单给吓着,其实系统应该给装备好了,除非真RP低,那你出门去买张彩票吧... ... 27 | 源码地址是https://github.com/php/php-src.git。官方推荐我们直接签出它的php-src目录: 28 | ````shell 29 | $ git clone https://github.com/php/php-src.git 30 | $ cd php-src 31 | 32 | ```` 33 | 34 | Windows的编译方式会稍有不同,设计也会复杂一些,这里不再描述。开发者可以自行试验。 35 | 其实你有很多办法安装PHP,最简单的一种就是从你系统的库或者源里通过apt-get、yum install之类的命令直接安装PHP7,这样做的好处你的系统可能会自动处理一些php在它上面的工作时的一些bug,而且你还可以方便的升级与卸载。这样做也有缺点,那就是你的PHP版本永远无法是最新的,通常www.php.net发布数周甚至数月后你才能用上相应的版本。 36 | 第二种方法:也是推荐使用的一种方法,那就是自行下载php-x.y.z.tar.gz的源码包,然后自行编译安装。这种包一般都是经过了海量的测试后才发布的,而且非常接近最新beta或者alpha版本。 37 | 此外,你还可以snaps.php.net提供的快照包来下载php进行编译安装,这个站点每几个小时便会从源码库里打包出一份新的PHP。不过从这取得的包可能会因为某个未经完整测试的代码提交而使PHP工作不正常。 38 | 39 | ## links 40 | * 4 [动手编译PHP](<4.md>) 41 | * 4.2 [PHP编译前的config配置](<4.2.md>) 42 | 43 | -------------------------------------------------------------------------------- /4.2.md: -------------------------------------------------------------------------------- 1 | # 4.2 动手编译PHP 2 | 3 | 第一章我们曾介绍过,PHP编译前的configure有两个特殊的选项,打开它们对我们开发PHP扩展或者进行PHP嵌入式开发时非常有帮助。但是当我们正常使用PHP的时候,则不应该开启这两个选项。 4 | ### --enable-debug 5 | 顾名思义,它的作用是激活调试模式。它将激活PHP源码中几个非常关键的函数,最典型的功能便是在每一个请求结束后给出这一次请求中内存的泄漏情况。 6 | 回顾一下第三章《内存管理》部分,php内核中的ZendMM( Zend Memory Manager)将会在每一个请求结束后强制释放在这个请求中申请的内存。 7 | 8 | ````c 9 | void show_value(int n) 10 | { 11 | char *message = emalloc(1024); 12 | 13 | sprintf(message, "The value of n is %d\n", n); 14 | php_printf("%s", message); 15 | } 16 | 17 | ```` 18 | 19 | 上面的代码执行后,将会导致1024B的内存泄漏,但是在ZendMM的帮助下,在请求结束后会被PHP内核自动的释放掉。 20 | 但是如果你开启了--enable-debug选项,在请求结束后内核便会给出一条信息,告知我们程序猿这次请求的内存泄漏情况。 21 | /cvs/php5/ext/sample/sample.c(33) :Freeing 0x084504B8 (1024 bytes), script=-
22 | === Total 1 memory leaks detected === 23 | 这条提示告知我们在这次请求结束后,ZendMM清理了泄漏的内存以及泄漏的内存位置。在它的帮助下,我们可以很快的定位到有问题的代码,然后通过efree等函数修正这个bug。 24 | 其实,内存泄漏并不是我们在开发中碰到的唯一错误,还有很多其它的bug很难被检测出来。有时候这些bug是致命的,但很难定位到出问题的代码。很多时候我们忙活了大半个晚上,修改了很多文件,最后make,但是当我们运行脚本的时候却得到下面的段错误。 25 | ````c 26 | $ sapi/cli/php -r 'myext_samplefunc();' 27 | Segmentation Fault 28 | //如果中文环境,则显示段错误 29 | 30 | ```` 31 | Orz...错误出在哪呢?我们遍历myext_samplefuc的所有实现代码也没有发现问题,扔进gdb里也仅仅显示几行无关紧要的信息而已。这种情况下,enable-debug就能帮你大忙了,打开这个选项后,你编译出来的php则会嵌入gdb或其它文件需要的所有调试信息。现在我们重新编译这个扩展,再扔进gdb里调试,便会得到如下的信息: 32 | ````c 33 | #0 0x1234567 php_myext_find_delimiter(str=0x1234567 "foo@#(FHVN)@\x98\xE0...", 34 | strlen=3, tsrm_ls=0x1234567) 35 | p = strchr(str, ','); 36 | 37 | ```` 38 | 现在所有的问题都水落石出了,字符串变量str没有以NULL结尾,而我们却把它当作一个参数传给了二进制不安全的字符串处理函数,str将会扫描str知道找到NULL为止,它的扫描肯定是越界了,然后引发了一个段错误。找到问题根源后,我们只要用memchr来替换strchr函数就能修复这个bug了。 39 | ### --enable-maintainer-zts 40 | 第二个重要的参数便是激活php的线程安全机制(Thread Safe Resource Manager(TSRM)/Zend Thread Safety(ZTS)),使我们开发出的程序是线程安全的。对于TRSM的介绍大家可以参考第一章的介绍,在平时的开发中,建议打开这个选项。 41 | ### --enable-embed 42 | 其实还有一个选项比较重要,那就是enable-embed,它主要用在你做php的嵌入式开发的场景中。平时我们把php作为apache的一个module进行编译,得到libphp5.so,而这个选项便使php编译后得到一个与我们设定的SAPI相对应的结果。 43 | 44 | 45 | ## links 46 | * 4.1 [编译前的准备](<4.1.md>) 47 | * 4.3 [Unix/Linux平台下的编译](<4.3.md>) 48 | 49 | -------------------------------------------------------------------------------- /4.3.md: -------------------------------------------------------------------------------- 1 | # 4.3 动手编译PHP 2 | 3 | 编译之前如果需要了解一下php的configure脚本的各个配置,./configure --help一下即可,或者参考一下网络上的资料。当你确定了应该开启哪几个选项,选项都应该赋什么值后,便可以开始正式的编译我们的PHP了。这里假设你下载了php-7.1.0的源码,而且你将其解压到/php-7.1.0/目录下。 4 | 进入终端,通过cd命令进入/php-7.1.0/目录,执行./configure脚本,然后make,make test,比如: 5 |
 6 | cd /php-7.1.0
 7 | ./configure --prefix=/walu/php/ --enable-debug --enable-maintainer-zts
 8 | make
 9 | make test
10 | make clean //自愿执行,非必须。
11 | 		
12 | make,尤其是make test命令是个耗时大户,具体执行时间的长短与机器配置有关(这两个命令做练习可以,如果我们部署开发环境的时候,建议大家用apt-get或者yum来安装现成的)。 13 | 14 | 15 | ## links 16 | * 4.2 [PHP编译前的config配置](<4.2.md>) 17 | * 4.4 [在Win32平台上编译PHP](<4.4.md>) 18 | 19 | -------------------------------------------------------------------------------- /4.4.md: -------------------------------------------------------------------------------- 1 | # 4.4 动手编译PHP 2 | 3 | Windows下的编译直接略过可以不看了。想自己研究如何在Windows的读者可以参看php官方文档说明。 4 | 5 | 注意,没翻译的这节仅代表作者05年的观点。 6 |
As with the UNIX build, the first step to preparing a Windows build is to unpack the source tarball. By default, Windows doesn't know what to do with a .tar.gz file. In fact, if you downloaded PHP using Internet Explorer, you probably noticed that it changed the name of the tarball file to php-5.1.0.tar.tar. This isn't IE craving a plate of fish sticks ordepending on who you aska bug, it's a "feature."
 7 | 
 8 | Start by renaming the file back to php-5.1.0.tar.gz (if necessary). If you have a program installed that is capable of reading .tar.gz files, you'll notice the icon immediately change. You can now double-click on the file to open up the decompression program. If the icon doesn't change, or if nothing happens when you double-click the icon, it means that you have no tar/gzip compatible decompression program installed. Check your favorite search engine for WinZIP, WinRAR, or any other application that is suitable for extracting .tar.gz archives.
 9 | 
10 | Whatever decompression program you use, have it decompress php-5.1.0.tar.gz to the root development folder you created earlier. This section will assume you have extracted it to C:\PHPDEV\ which, because the zip file contains a folder structure, will result in the source tree residing in C:\PHPDEV\php-5.1.0.
11 | 
12 | After it's unpacked, open up a build environment window by choosing Start, All Programs, Microsoft Platform SDK for Windows Server 2003 SP1, Open Build Environment Window, Windows 2000 Build Environment, Set Windows 2000 Build Environment (Debug). The specific path to this shortcut might be slightly different depending on the version of the Platform SDK you have installed and the target platform you will be building for (2000, XP, 2003).
13 | 
14 | A simple command prompt window will open up stating the target build platform. This command prompt has most, but not all, necessary environment variables set up. You'll need to run one extra batch file in order to let the PHP build system know where Visual C++ Express is. If you accepted the default installation location this batch file will be located at C:\Program Files\Microsoft Visual Studio 8\VC\bin\vcvars32.bat. If you can't find vcvars32.bat, check the same directoryor its parentfor vcvarsall.bat. Just be sure to run it inside the same command prompt window you just opened. It will set additional environment variables that the build process will need.
15 | 
16 | Now, change the directory to the location where you unpacked PHP
17 | 
18 | C:\PHPDEV\php-5.1.0and run buildconf.bat.
19 | 
20 | C:\Program Files\Microsoft Platform SDK> cd \PHPDEV\php-5.1.0
21 | C:\PHPDEV\php-5.1.0> buildconf.bat
22 | 
23 | 
24 | If all is going well so far you'll see the following two lines of output:
25 | 
26 | Rebuilding configure.js
27 | Now run 'cscript /nologo configure.js help'
28 | 
29 | 
30 | At this point, you can do as the message says and see what options are available. The enable-maintainer-zts option is not necessary here because the Win32 build automatically assumes that ZTS will be required by any SAPI. If you wanted to turn it off, you could issue disable-zts, but that's not the case here because you're building for a development environment anyway.
31 | 
32 | In this example I've removed a few other extensions that aren't relevant to extension and embedding development for the sake of simplicity. If you'd like to rebuild PHP using additional extensions, you'll need to hunt down the libraries on which they depend.
33 | 
34 | C:\php-5.1.0> cscript /nologo configure.js without-xml without-wddx \
35 | without-simplexml without-dom without-libxml disable-zlib \
36 | without-sqlite disable-odbc disable-cgi enable-cli \
37 | enable-debug without-iconv
38 | 
39 | 
40 | Again, a stream of informative output will scroll by, followed by instructions to execute the final command:
41 | 
42 | C:\php-5.1.0> nmake
43 | 
44 | 
45 | Finally, a working build of PHP compiled for the Win32 platform.
46 | 
47 | 48 | 49 | ## links 50 | * 4.3 [Unix/Linux平台下的编译](<4.3.md>) 51 | * 4.5 [第四章小结](<4.5.md>) 52 | 53 | -------------------------------------------------------------------------------- /4.5.md: -------------------------------------------------------------------------------- 1 | # 4.5 动手编译PHP 2 | 3 | 单就开发一个最基本的php扩展来说,该掌握的前置知识我们已经都掌握了。在接下来的章节里我们将会深入的研究如何制作一个PHP扩展,以及制作一个优秀的PHP扩展所需的其它知识。 4 | 此外,如果你只想把PHP当作一个嵌入式应用来使用,我们也强烈的建议你不要直接跳到最后几章,因为在接下来的章节里我们将详细的介绍与PHP内核密切相关的一些内容,比如HashTable、数组、对象......等等的实现方式与应用方法。 5 | 6 | 7 | 8 | ## links 9 | * 4.4 [在Win32平台上编译PHP](<4.4.md>) 10 | * 5 [Your First Extension](<5.md>) 11 | 12 | -------------------------------------------------------------------------------- /4.md: -------------------------------------------------------------------------------- 1 | # 4 动手编译PHP 2 | 3 | * [1. 编译前的准备](4.1.md) 4 | * [2. PHP编译前的config配置](4.2.md) 5 | * [3. Unix/Linux平台下的编译](4.3.md) 6 | * [4. 在Win32平台上编译PHP](4.4.md) 7 | * [5. 小结](4.5.md) 8 | 9 | 到现在为止,你肯定应该在至少一种平台上安装过PHP,并用它来开发你的web程序了。你可能下载的win32平台下的iis或者apache对应的安装包,也可能使用了由第三方提供的linux、bsd等平台下的二进制包。而现在,则是我们动手自己编译PHP的时候了。这也是我们动手开发第一个扩展的最后一项准备知识了。 10 | 强烈推荐你在Linux下调试本章的程序,因为win部分我还没有翻译,:-) 11 | 12 | 13 | ## links 14 | * 3.3 [3.3 第三章总结](<3.3.md>) 15 | * 4.1 [编译前的准备](<4.1.md>) 16 | 17 | -------------------------------------------------------------------------------- /5.1.md: -------------------------------------------------------------------------------- 1 | # 5.1 Your First Extension 2 | (TBD) 3 | 4 | ### 配置文件 5 | 才开始,我们先用最快的(不是最标准的)的方式来建立一个代码最少的扩展。在php源码文件夹的ext目录下创建一个新的文件夹,这里我取的名字叫做walu,它往往就是我们扩展的名字。其实这个文件夹可以放在任何一个位置,但是为了我们在后面介绍win32的编译与静态编译,我们还是把它放在php源码的ext目录下。 6 | 现在,我们在这个目录下创建一个config.m4文件,并输入以下内容: 7 | (php5) 8 | 9 | 10 | PHP_ARG_ENABLE(walu, 11 | [Whether to enable the "walu" extension], 12 | [ enable-walu Enable "walu" extension support]) 13 | 14 | if test $PHP_WALU != "no"; then 15 | PHP_SUBST(WALU_SHARED_LIBADD) 16 | PHP_NEW_EXTENSION(walu, walu.c, $ext_shared) 17 | fi 18 | 19 | 20 | (php7) 21 | 22 | 23 | ````c 24 | PHP_ARG_ENABLE(walu, whether to enable walu support, 25 | Make sure that the comment is aligned: 26 | [ --enable-walu Enable walu support]) 27 | 28 | if test "$PHP_WALU" != "no"; then 29 | PHP_NEW_EXTENSION(walu, walu.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) 30 | fi 31 | ```` 32 | 33 | 上面PHP_ARG_ENABLE函数有三个参数,第一个参数是我们的扩展名(注意不用加引号),第二个参数是当我们运行./configure脚本时显示的内容,最后一个参数则是我们在调用./configure --help时显示的帮助信息。 34 | 35 | > 也许有人会问,为什么有的扩展的开启方式是 --enable-extname的形式,有的则是--with-extname的形式呢?其实两者并没有什么本质的不同,只不过enable多代表不依赖外部库便可以直接编译,而with大多需要依赖于第三方的lib。 36 | > 现在,我们的扩展并不需要依赖其它的库文件,所以我们直接使用--enable-walu便可以了。在第17章的时候我们将接触通过CFLAGS和LDFLAGS来配置自己的扩展,使其依赖第三方库文件才能被编译成php扩展。 37 | 38 | 如果我们显示运行./configure --enable-walu,那么终端环境便会自动将$PHP_WALU变量设置为yes,而PHP_SUBST函数只不过是php官方对autoconf里的AC_SUBST函数的一层封装。 39 | 最后重要的一点是,PHP_NEW_EXTENSION函数声明了这个扩展的名称、需要的源文件名、此扩展的编译形式。如果我们的扩展使用了多个文件,便可以将这多个文件名罗列在函数的参数里,如: 40 | 41 | PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared) 42 | 43 | 最后的$ext_shared参数用来声明这个扩展不是一个静态模块,而是在php运行时动态加载的。 44 | 45 | 46 | 下面,我们来编写实现扩展主逻辑的源文件walu.c: 47 | ````c 48 | //加载config.h,如果配置了的话 49 | #ifdef HAVE_CONFIG_H 50 | #include "config.h" 51 | #endif 52 | 53 | //加载php头文件 54 | #include "php.h" 55 | 56 | 57 | #define phpext_walu_ptr &walu_module_entry 58 | 59 | //module entry 60 | zend_module_entry walu_module_entry = { 61 | #if ZEND_MODULE_API_NO >= 20010901 62 | STANDARD_MODULE_HEADER, 63 | #endif 64 | "walu", //这个地方是扩展名称,往往我们会在这个地方使用一个宏。 65 | NULL, /* Functions */ 66 | NULL, /* MINIT */ 67 | NULL, /* MSHUTDOWN */ 68 | NULL, /* RINIT */ 69 | NULL, /* RSHUTDOWN */ 70 | NULL, /* MINFO */ 71 | #if ZEND_MODULE_API_NO >= 20010901 72 | "2.1", //这个地方是我们扩展的版本 73 | #endif 74 | STANDARD_MODULE_PROPERTIES 75 | }; 76 | 77 | #ifdef COMPILE_DL_WALU 78 | ZEND_GET_MODULE(walu) 79 | #endif 80 | 81 | ```` 82 | 这就是所有的代码了,不过鉴于我们平时的开发习惯,往往会把这一份代码分成两份,一个.h文件,一个.c文件。上面的代码只是生成了一基本的框架,而没有任何实际的用处。 83 | 紧接着,创建一个zend_module_entry结构体,你肯定已经发现了,依据ZEND_MODULE_API_NO 是否大于等于 20010901,这个结构体需要不同的定义格式。20010901大约代表PHP4.2.0版本,所以我们现在的扩展几乎都要包含STANDARD_MODULE_HEADER这个元素了。 84 | 其余六个成员我们可以先赋值为NULL,其实看看它们各自后面的注释你就应该大体上了解它们各自是负责哪一方面的工作了。 85 | 最后,最底下的代码用来标志我们的这个扩展是一个shared module。它是干么的呢?我也说不清楚,反正带上就对了,否则扩展会工作不正常。原文解释:This brief conditional simply adds a reference used by Zend when your extension is loaded dynamically. Don't worry about what it does or how it does it too much; just make sure that it's around or the next section won't work. 86 | ### 标准一些 87 | 根据我们平时的开发习惯,应该不会把所有代码都写在这一个文件里的,我们需要把上述代码放在两个文件里,一个头文件,一个c文件。 88 | ````c 89 | //php_walu.h 90 | #ifndef WALU_H 91 | #define WALU_H 92 | 93 | //加载config.h,如果配置了的话 94 | #ifdef HAVE_CONFIG_H 95 | #include "config.h" 96 | #endif 97 | 98 | //加载php头文件 99 | #include "php.h" 100 | #define phpext_walu_ptr &walu_module_entry 101 | extern zend_module_entry walu_module_entry; 102 | 103 | #endif 104 | 105 | ```` 106 | 下面的是c文件 107 | ````c 108 | //walu.c 109 | #include "php_walu.h" 110 | //module entry 111 | zend_module_entry walu_module_entry = { 112 | #if ZEND_MODULE_API_NO >= 20010901 113 | STANDARD_MODULE_HEADER, 114 | #endif 115 | "walu", //这个地方是扩展名称,往往我们会在这个地方使用一个宏。 116 | NULL, /* Functions */ 117 | NULL, /* MINIT */ 118 | NULL, /* MSHUTDOWN */ 119 | NULL, /* RINIT */ 120 | NULL, /* RSHUTDOWN */ 121 | NULL, /* MINFO */ 122 | #if ZEND_MODULE_API_NO >= 20010901 123 | "2.1", //这个地方是我们扩展的版本 124 | #endif 125 | STANDARD_MODULE_PROPERTIES 126 | }; 127 | 128 | #ifdef COMPILE_DL_WALU 129 | ZEND_GET_MODULE(walu) 130 | #endif 131 | 132 | ```` 133 | 134 | (PHP7) 135 | 136 | ````c 137 | 138 | #ifndef PHP_WALU_H 139 | #define PHP_WALU_H 140 | 141 | extern zend_module_entry walu_module_entry; 142 | #define phpext_walu_ptr &walu_module_entry 143 | 144 | #define PHP_WALU_VERSION "0.1.0" 145 | 146 | #ifdef PHP_WIN32 147 | # define PHP_WALU_API __declspec(dllexport) 148 | #elif defined(__GNUC__) && __GNUC__ >= 4 149 | # define PHP_WALU_API __attribute__ ((visibility("default"))) 150 | #else 151 | # define PHP_WALU_API 152 | #endif 153 | 154 | #ifdef ZTS 155 | #include "TSRM.h" 156 | #endif 157 | 158 | 159 | #define WALU_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(walu, v) 160 | 161 | #if defined(ZTS) && defined(COMPILE_DL_WALU) 162 | ZEND_TSRMLS_CACHE_EXTERN() 163 | #endif 164 | 165 | #endif 166 | 167 | ```` 168 | 169 | ````c 170 | 171 | #ifdef HAVE_CONFIG_H 172 | #include "config.h" 173 | #endif 174 | 175 | #include "php.h" 176 | #include "php_ini.h" 177 | #include "ext/standard/info.h" 178 | #include "php_walu.h" 179 | 180 | 181 | zend_module_entry walu_module_entry = { 182 | STANDARD_MODULE_HEADER, 183 | "walu", 184 | NULL, 185 | NULL, 186 | NULL, 187 | NULL, /* Replace with NULL if there's nothing to do at request start */ 188 | NULL, /* Replace with NULL if there's nothing to do at request end */ 189 | NULL, 190 | PHP_WALU_VERSION, 191 | STANDARD_MODULE_PROPERTIES 192 | }; 193 | 194 | #ifdef COMPILE_DL_WALU 195 | #ifdef ZTS 196 | ZEND_TSRMLS_CACHE_DEFINE() 197 | #endif 198 | ZEND_GET_MODULE(walu) 199 | #endif 200 | 201 | ```` 202 | 203 | ## links 204 | * 5 [Your First Extension](<5.md>) 205 | * 5.2 [编译我们的扩展](<5.2.md>) 206 | 207 | -------------------------------------------------------------------------------- /5.2.md: -------------------------------------------------------------------------------- 1 | # 5.2 Your First Extension 2 | 3 | 我们已经在上一节准备好了需要编译的源文件,接下来需要的便是把它们编译成目标文件了。因为在*nix平台和win平台下的编译步骤有些差异,所以这个地方需要分成两块介绍,很不幸,win部分还没有整理,请随时关注本项目。 4 | ### 在*nix下编译 5 | 第一步:我们需要根据config.m4文件生成一个configure脚本、Makefile等文件,这一步有phpize来帮我们做: 6 | 7 | (php5) 8 | 9 | ````c 10 | $ phpize 11 | PHP Api Version: 20041225 12 | Zend Module Api No: 20050617 13 | Zend Extension Api No: 220050617 14 | 15 | ```` 16 | 17 | 18 | (php7) 19 | 20 | ````c 21 | 22 | bruce:walu fzq$ phpize 23 | Configuring for: 24 | PHP Api Version: 20151012 25 | Zend Module Api No: 20151012 26 | Zend Extension Api No: 320151012 27 | 28 | ```` 29 | 30 |
31 | The extra 2 at the start of Zend Extension Api No isn't a typo; it corresponds to the Zend Engine 2 version and is meant to keep this API number greater than its ZE1 counterpart.
32 | 现在查看一下我们扩展所在的目录,会发现多了许多文件。phpize程序根据config.m4里的信息生成了许多编译php扩展必须的文件,比如生成makefiles等,这为我们省了很多的麻烦。 33 | 接下来我们运行./configure脚本,这里我们并不需要再注明enable-maintainer-zts、enable-debug等参数,phpize程序会自动的去已经编译完成的php核心里获取这几个参数的值。 34 | 接下来就像我们安装其它程序一样执行make; make test;即可,如果没有错误,那么在module文件夹下面便会生成我们的目标文件 —— walu.so。 35 | ### 在windows平台下编译s 36 | The config.m4 file you created earlier was actually specific to the *nix build. In order to make your extension compile under Windows, you'll need to create a separatebut similarconfiguration file for it. 37 | Add config.w32 with the following contents to your ext/sample directory: 38 | ````c 39 | ARG_ENABLE("sample", "enable sample extension", "no"); 40 | if (PHP_SAMPLE != "no") { 41 | EXTENSION("sample", "sample.c"); 42 | } 43 | 44 | ```` 45 | As you can see, this file bears a resemblance on a high level to config.m4. The option is declared, tested, and conditionally used to enable the build of your extension. 46 | Now you'll repeat a few of the steps you performed in Chapter 4, "Setting Up a Build Environment," when you built the PHP core. Start by opening up a build window from the Start menu by selecting All Programs, Microsoft Platform SDK for Windows Server 2003 SP1, Open Build Environment Window, Windows 2000 Build Environment, Set Windows 2000 Build Environment (Debug), and running the C:\Program Files\Microsoft Visual Studio 8\VC\bin\vcvars32.bat batch file. 47 | Remember, your installation might require you to select a different build target or run a slightly different batch file. Refer to the notes in the corresponding section of Chapter 4 to refresh your memory. 48 | Again, you'll want to go to the root of your build directory and rebuild the configure script. 49 | ````c 50 | C:\Program Files\Microsoft Platform SDK> cd \PHPDEV\php-5.1.0 51 | C:\PHPDEV\php-5.1.0> buildconf.bat 52 | Rebuilding configure.js 53 | Now run 'cscript /nologo configure.js help' 54 | 55 | ```` 56 | This time, you'll run the configure script with an abridged set of options. Because you'll be focusing on just your extension and not the whole of PHP, you can leave out options pertaining to other extensions; however, unlike the Unix build, you do need to include the enable-debug switch explicitly even though the core build already has it. 57 | The only crucial switch you'll need hereapart from debug of courseis enable-sample=shared. The shared option is required here because configure.js doesn't know that you're planning to build sample as a loadable extension. Your configure line should therefore look something like this: 58 | ````c 59 | C:\PHPDEV\php-5.1.0> cscript /nologo configure.js \ 60 | enable-debug enable-sample=shared 61 | 62 | ```` 63 | 64 |
65 | Recall that enable-maintainer-zts is not required here as all Win32 builds assume that ZTS must be enabled. Options relating to SAPIssuch as embedare also not required here as the SAPI layer is independent from the extension layer. 66 |
67 | Lastly, you're ready to build the extension. Because this build is based from the coreunlike the Unix extension build, which was based from the extensionyou'll need to specify the target name in your build line. 68 | 69 | C:\PHPDEV\php-5.1.0> nmake php_sample.dll 70 | Once compilation is complete, you should have a working php_sample.dll binary ready to be used in the next step. Remember, because this book focuses on *nix development, the extension will be referred to as sample.so rather than php_sample.dll in all following text. 71 | Loading an Extension Built as a Shared Module 72 | ### 加载扩展 73 | 为了使PHP能够找到需要的扩展文件,我们需要把编译好的so文件或者dll文件复制到PHP的扩展目录下,它的地址我们可以通过phpinfo()输出的信息找到,也可以在php.ini文件里进行配置找到并配置,名称为:extension_dir的值。默认情况下,php.ini文件位于/usr/local/lib/php.ini或者C:\windows\php.ini(现在由于fastcgi模式居多,在win平台上php.ini越来越多的直接存在于php-cgi.exe程序所在目录下)。如果找不到,我们可以通过php -i 命令或者 80 | 81 | ```` 82 | 83 | 如果在输出中我们没有找到walu.so,那肯定是哪里出问题了。这时候我们需要根据程序的输出信息去查找错误。 84 | 上面这样每次使用扩展都需要先dl一下真是太麻烦了,其实我们有更好的办法让php运行时自动加载我们的扩展。那就是在php.ini里这样配置: 85 | ````c 86 | extension_dir=/usr/local/lib/php/modules/ 87 | extension=walu.so 88 | 89 | ```` 90 | 这样只要我们把walu.so这个文件放置在extension_dir配置的目录下,php就会在每次启动的时候自动加载了。这样我们就可以像我们平时使用curl、Mysql扩展一样直接使用,而不用麻烦的调用dl函数了。 91 | 备注: 92 | 以下的章节我们都默认使用上面的这种方式来加载我们的扩展,而不是调用dl函数。 93 | 94 | 95 | ## links 96 | * 5.1 [一个扩展的基本结构](<5.1.md>) 97 | * 5.3 [静态编译](<5.3.md>) 98 | 99 | -------------------------------------------------------------------------------- /5.3.md: -------------------------------------------------------------------------------- 1 | # 5.3 Your First Extension 2 | 3 | 我们检查一下PHP语言中get_loaded_extensions()函数的输出,会发现有一些扩展并没有php.ini文件中调用,而它们确实也已经加载到PHP里去了,可以让我们在PHP语言中使用,如standard、Reflection、Core等。它们便是静态编译的,它们没有被编译成so或者dll文件供PHP动态调用,而是直接和PHP主程序编译到一起。 4 | ### 在*nix上执行静态编译 5 | 现在,先让我们执行一下PHP源码根目录下的./configure --help命令。会发现输出信息并没有包含我们的扩展,这是因为这个configure脚本生成的时候,我们的扩展还没有编写呢。(这个configure是PHP官方分发的。),所以首先我们需要使用buildconf命令生成新的configure脚本。 6 | $ ./buildconf --force 7 | 8 |
If you're using a production release of PHP to do development against, you'll find that ./buildconf by itself doesn't actually work. In this case you'll need to issue: ./buildconf force to bypass some minor protection built into the ./configure command.
9 | 现在当我们再执行./configure --help的时候,便会发现walu扩展的信息已经出现了。现在我们只需要重新走一遍PHP的编译过程,便可以把我们的扩展以静态编译的方式加入到PHP主程序中了。哦,千万不要忘记使用--enable-walu参数开启我们的扩展。 10 | 当然,对于我们学习如何开发PHP扩展来讲,静态编译可不是一个好主意,因为如果采用静态编译的方式,只要我们的扩展做了改动,便需要重新编译整个PHP才行,这个过程太痛苦了。还是用前一节的方式吧。但是这种方式有利于提高性能,所以如果我们是在部署生产环境,则可以考虑! 11 | ### Building Statically Under Windows 12 | Regenerating the configure.js script for Windows follows the same pattern as regenerating the ./configure script for *nix. Navigate to the root of the PHP source tree and reissue buildconf.bat as you did in Chapter 4. 13 | The PHP build system will scan for config.w32 files, including the one you just made for ext/sample, and generate a new configure.js script with which to build a static php binary. 14 | 15 | 16 | ## links 17 | * 5.2 [编译我们的扩展](<5.2.md>) 18 | * 5.4 [编写函数](<5.4.md>) 19 | 20 | -------------------------------------------------------------------------------- /5.4.md: -------------------------------------------------------------------------------- 1 | # 5.4 Your First Extension 2 | 3 | 前面我们已经生成好了一份扩展框架,但它是没有什么实际作用的。一个扩展的作用可大了去了,既可以操作PHP中的变量、常量,还可以定义函数、类、方法、资源等。先让我们从函数说起吧! 4 | ### ZEND_FUNCTION()宏函数 5 | ZEND_FUNCTION()宏函数也可以写成PHP_FUNCTION(),但ZEND_FUNCTION()更前卫、标准一些,但两者是完全相同的。 6 | ````c 7 | #define PHP_FUNCTION ZEND_FUNCTION 8 | 9 | #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name)) 10 | #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) 11 | #define ZEND_FN(name) zif_##name 12 | 13 | ```` 14 | 其中,zif是zend internal function的意思,zif_前缀是可供PHP语言调用的函数在C语言中的函数名称前缀。 15 | ````c 16 | ZEND_FUNCTION(walu_hello) 17 | { 18 | php_printf("Hello World!\n"); 19 | } 20 | 21 | ```` 22 | 上面定义了一个函数,在C语言中展开后应该是这样的: 23 | ````c 24 | void zif_walu_hello(INTERNAL_FUNCTION_PARAMETERS) 25 | { 26 | php_printf("Hello World!\n"); 27 | } 28 | 29 | ```` 30 | 上面的展开式仅供参考,绝不推荐在编程时使用,我们应该采用宏的形式,来提高程序的兼容性与可读性。 31 | 上面的代码定义了一个可供用户在PHP语言中调用的函数实现,但现在用户还不能在程序中调用,因为这个函数还没有与用户端建立联系,也就是说虽然我们在C中完成了它的实现,但用户端PHP语言还根本不知道它的存在呢。 32 | 现在我们回头看一下5.1节中我们为扩展定义的zend_module_entry walu_module_entry(它是联系C扩展与PHP语言的重要纽带)中的“NULL, /* Functions */”,当时我们为它赋予了NULL,是因为还没有函数,现在我们已经为它编写了函数了,便可以给它赋予一个新值了,这个值需要是zend_function_entry[]类型的,首先让我们来构造这个重要数据。 33 | ````c 34 | static zend_function_entry walu_functions[] = { 35 | ZEND_FE(walu_hello, NULL) 36 | { NULL, NULL, NULL } 37 | }; 38 | 39 | /* 40 | 下面是ZEND_FE的定义 41 | #define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0) 42 | #define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags }, 43 | 44 | ZEND_FE(walu_hello, NULL)展开后便是: 45 | {"walu_hello",zif_walu_hello,NULL, (zend_uint) (sizeof(NULL)/sizeof(struct _zend_arg_info)-1), 0 }, 46 | 47 | */ 48 | 49 | ```` 50 | 51 | 其中最后的{NULL,NULL,NULL}是固定不变的。ZEND_FE()宏函数是对我们walu_hello函数的一个声明,如果我们有多个函数,可以直接以类似的形式添加到{NULL,NULL,NULL}之前,注意每个之间不需要加逗号。 52 | 其中的arg_info我们现在先赋予NULL就行了,我们将在第7章讨论这个参数。确保一切无误后,我们替换掉zend_module_entry里的原有成员,现在应该是这样的: 53 | ````c 54 | ZEND_FUNCTION(walu_hello) 55 | { 56 | php_printf("Hello World!\n"); 57 | } 58 | 59 | static zend_function_entry walu_functions[] = { 60 | ZEND_FE(walu_hello, NULL) 61 | { NULL, NULL, NULL } 62 | }; 63 | 64 | zend_module_entry walu_module_entry = { 65 | #if ZEND_MODULE_API_NO >= 20010901 66 | STANDARD_MODULE_HEADER, 67 | #endif 68 | "walu", //这个地方是扩展名称,往往我们会在这个地方使用一个宏。 69 | walu_functions, /* Functions */ 70 | NULL, /* MINIT */ 71 | NULL, /* MSHUTDOWN */ 72 | NULL, /* RINIT */ 73 | NULL, /* RSHUTDOWN */ 74 | NULL, /* MINFO */ 75 | #if ZEND_MODULE_API_NO >= 20010901 76 | "2.1", //这个地方是我们扩展的版本 77 | #endif 78 | STANDARD_MODULE_PROPERTIES 79 | }; 80 | 81 | ```` 82 | 现在configure、make、make test,复制到extension dir。用下面这个命令来测试下,应该会输出hello world了,如果没有输出,说明你哪个地方做错了,查不出来的话可以给我发mail,看看是不是特例:-) 83 | ````c 84 | $ php -r 'walu_hello();' 85 | 86 | ```` 87 | ## Zend Internal Functions 88 | zif_前缀在前面我们已经说过了,代表着"Zend Internal Function",主要用来避免命名冲突,比如PHP语言中有个strlen()函数,而C语言中也有strlen()函数,所以PHP中的strlen在C中的实现不能是strlen,而应改是一个不同的名字。 89 | 但是有些时候尽管我们加了zif_前缀,还会出现一些冲突问题。比如函数名称本身是一个宏名称从而被编译器替换掉了。在这种情况下,我们需要手动来为我们扩展中的函数命名,这一步操作通过ZEND_NAMED_FUNCTION(diy_walu_hello)来代替ZEND_FUNCTION(hello_hello)。前者由我们指定名称,后者自己加上前缀。 90 | 如果我们在定义函数时使用了ZEND_NAMED_FUNCTION(),那么在walu_functions[]里,我们需要用ZEND_NAMED_FE()宏来代替ZEND_FE()宏。即:ZEND_NAMED_FE(walu_hello,diy_walu_hello,NULL) 91 | 上面的技术在ext/standard/file.c用到了,我们可以看fopen()函数的定义:PHP_NAMED_FUNCTION(php_if_fopen)。但是用户端不会感觉到任何变化,还是用fopen函数来使用,因为zend_function_entry中每一项的第一个值代表这此函数在PHP语言中的名称。Internally, however, the function is protected from being mangled by preprocessor macros and over-helpful compilers.(原作者说的这个理由我也没看明白,请知者指点) 92 | ## Function Aliases 93 | 去PHP手册里查一下pos()函数,会得到这么一条信息:"This function is an alias of: current()";也就是说,它只是current的一个软链接而已,类似linux中的ln -s命令,理解成win下的快捷方式也成。运行pos函数,其实就是在运行current函数,转接了一下而已。这往往是因为版本升级引起的,新版本中的程序提供了某个功能的新的实现,先为原来的函数改个名,但还需要保留原来的函数名,所以这就用到了alias。这个功能可以在内核中通过ZEND_NAMED_FE宏来实现。 94 | ````c 95 | static zend_function_entry walu_functions[] = { 96 | ZEND_FE(walu_hello, NULL) 97 | ZEND_NAMED_FE(walu_hi, ZEND_FN(walu_hello), NULL) 98 | { NULL, NULL, NULL } 99 | }; 100 | 101 | /* 102 | ZEND_NAMED_FE也可以写成PHP_NAMED_FE,但推荐用前者 103 | #define ZEND_NAMED_FE(zend_name, name, arg_info) ZEND_FENTRY(zend_name, name, arg_info, 0) 104 | */ 105 | 106 | ```` 107 | 通过ZEND_NAMED_FE的展开式我们了解到,它只是把PHP语言中的两个函数的名字对应到同一个C语言函数而已。 108 | 其实还有另外一种写法: 109 | ````c 110 | static zend_function_entry walu_functions[] = { 111 | ZEND_FE(walu_hello, NULL) 112 | ZEND_FALIAS(walu_hi,walu_hello, NULL) 113 | { NULL, NULL, NULL } 114 | }; 115 | 116 | /* 117 | #define ZEND_FALIAS(name, alias, arg_info) ZEND_FENTRY(name, ZEND_FN(alias), arg_info, 0) 118 | */ 119 | 120 | ```` 121 | 展开式是一样的,真不清楚官方鼓捣这么多同样的宏干啥。 122 | ````php 123 | ) 132 | * 5.5 [第五章小结](<5.5.md>) 133 | 134 | -------------------------------------------------------------------------------- /5.5.md: -------------------------------------------------------------------------------- 1 | # 5.5 Your First Extension 2 | 3 | 在这一章里,我们学会了如何创建一个PHP框架并为其添加函数,并编译到PHP中供用户在PHP语言中调用。在接下来的章节里,我们将陆续看到许多高级的PHP内核特性,从而使我们编写出更好的PHP扩展。 4 | 编译PHP源码的环境会随着平台与时间的不同而变化,如果本章讲述的知识无法使你顺利的编译PHP,那你可以给我发信,或者去php.net寻找答案,当然最简单的方法是Google,切记的是,万一Google抽风,不要忘了还有Baidu。 5 | 6 | 7 | ## links 8 | * 5.4 [编写函数](<5.4.md>) 9 | * 6 [函数返回值](<6.md>) 10 | 11 | -------------------------------------------------------------------------------- /5.md: -------------------------------------------------------------------------------- 1 | # 5 Your First Extension 2 | 3 | * [1. 一个扩展的基本结构](5.1.md) 4 | * [2. 编译我们的扩展](5.2.md) 5 | * [3. 静态编译](5.3.md) 6 | * [4. 编写函数](5.4.md) 7 | * [5. 小结](5.5.md) 8 | 9 | 每一个PHP扩展都至少需要两个文件:一个配置文件和一个源文件。配置文件用来告诉编译器应该编译哪几个文件,以及编译本扩展是否需要的其它lib。 10 | 11 | 12 | ## links 13 | * 4.5 [第四章小结](<4.5.md>) 14 | * 5.1 [一个扩展的基本结构](<5.1.md>) 15 | 16 | -------------------------------------------------------------------------------- /6.1.md: -------------------------------------------------------------------------------- 1 | # 6.1 函数返回值 2 | 3 | 你也许会认为扩展中定义的函数应该直接通过return关键字来返回一个值,比如由你自己来生成一个zval并返回,就像下面这样: 4 | ````c 5 | ZEND_FUNCTION(sample_long_wrong) 6 | { 7 | zval *retval; 8 | 9 | MAKE_STD_ZVAL(retval); 10 | ZVAL_LONG(retval, 42); 11 | 12 | return retval; 13 | } 14 | 15 | ```` 16 | 但是,上面的写法是无效的!与其让扩展开发员每次都初始化一个zval并return之,zend引擎早就准备好了一个更好的方法。它在每个zif函数声明里加了一个zval*类型的形参,名为return_value,专门来解决返回值这个问题。在前面我们已经知道了ZEND_FUNCTION宏展开后是void name(INTERNAL_FUNCTION_PARAMETERS)的形式,现在是我们展开代表参数声明的INTERNAL_FUNCTION_PARAMETERS宏的时候了。 17 | 18 | (php5) 19 | ````c 20 | #define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC 21 | 22 | ```` 23 |
    24 |
  • int ht
  • 25 |
  • zval *return_value,我们在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。
  • 26 |
  • zval **return_value_ptr,
  • 27 |
  • zval *this_ptr,如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$this变量差不多。
  • 28 |
  • int return_value_used,代表用户端在调用此函数时有没有使用到它的返回值。
  • 29 |
30 | 31 | (php7) 32 | 33 | #define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value 34 | 35 | 下面让我们先试验一个非常简单的例子,我先给出PHP语言中的实现,然后给出我们在扩展中用C语言完成相同功能的代码。 36 | ````php 37 | 48 | 49 | ```` 50 | 下面是我们在编写扩展时的实现。 51 | ````c 52 | ZEND_FUNCTION(sample_long) 53 | { 54 | ZVAL_LONG(return_value, 42); 55 | return; 56 | } 57 | 58 | ```` 59 | 需要注意的是,ZEND_FUNCTION本身并没有通过return关键字返回任何有价值的东西,它只不过是在运行时修改了return_value指针所指向的变量的值而已,而内核则会把return_value指向的变量作为用户端调用此函数后的得到的返回值。回想一下,ZVAL_LONG()宏是对一类操作的封装,展开后应该就是下面这样: 60 | ````c 61 | Z_TYPE_P(return_value) = IS_LONG; 62 | Z_LVAL_P(return_value) = 42; 63 | 64 | //更彻底的讲,应该是这样的: 65 | return_value->type = IS_LONG; 66 | return_value->value.lval = 42; 67 | 68 | ```` 69 | 70 |
我们千万不要自己去修改return_value的is_ref__gc和refcount__gc属性,这两个属性的值会由PHP内核自动管理。
71 | 现在我们把它加到我们在第五章得到的那个扩展框架里,并把这个函数名称注册到函数入口数组里,就像下面这样: 72 | ````c 73 | static zend_function_entry walu_functions[] = { 74 | ZEND_FE(walu_hello, NULL) 75 | PHP_FE(sample_long, NULL) 76 | { NULL, NULL, NULL } 77 | }; 78 | 79 | ```` 80 | 现在我们编译我们的扩展,便可以在用户端通过调用sample_long函数来得到一个整型的返回值了: 81 | ````php 82 | 83 | 84 | ```` 85 | ### 与return_value有关的宏 86 | return_value如此重要,内核肯定早已经为它准备了大量的宏,来简化我们的操作,提高程序的质量。 87 | 在前几章我们接触的宏大多都是以ZVAL_开头的,而接下来我们要介绍的宏的名字是:RETVAL。 88 | 再回到上面的那个例子,我们用RETVAL来重写一下: 89 | ````c 90 | PHP_FUNCTION(sample_long) 91 | { 92 | RETVAL_LONG(42); 93 | //展开后相当与ZVAL_LONG(return_value, 42); 94 | return; 95 | } 96 | 97 | ```` 98 | 大多数情况下,我们在处理完return_value后所做的便是用return语句结束我们的函数执行,帮人帮到底,送佛送到西,为了减少我们的工作量,内核中还提供了RETURN_*系列宏来为我们自动补上return;如: 99 | 100 | PHP_FUNCTION(sample_long) 101 | { 102 | RETURN_LONG(42); 103 | //#define RETURN_LONG(l) { RETVAL_LONG(l); return; } 104 | php_printf("I will never be reached.\n"); //这一行代码永远不会被执行。 105 | } 106 | 107 | 下面,我们给出目前所有的RETVAL_***宏和RETURN_***宏,供大家查阅使用。 108 | ````c 109 | //这些宏都定义在Zend/zend_API.h文件里 110 | #define RETVAL_RESOURCE(l) ZVAL_RESOURCE(return_value, l) 111 | #define RETVAL_BOOL(b) ZVAL_BOOL(return_value, b) 112 | #define RETVAL_NULL() ZVAL_NULL(return_value) 113 | #define RETVAL_LONG(l) ZVAL_LONG(return_value, l) 114 | #define RETVAL_DOUBLE(d) ZVAL_DOUBLE(return_value, d) 115 | #define RETVAL_STRING(s, duplicate) ZVAL_STRING(return_value, s, duplicate) 116 | #define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate) 117 | #define RETVAL_EMPTY_STRING() ZVAL_EMPTY_STRING(return_value) 118 | #define RETVAL_ZVAL(zv, copy, dtor) ZVAL_ZVAL(return_value, zv, copy, dtor) 119 | #define RETVAL_FALSE ZVAL_BOOL(return_value, 0) 120 | #define RETVAL_TRUE ZVAL_BOOL(return_value, 1) 121 | 122 | #define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; } 123 | #define RETURN_BOOL(b) { RETVAL_BOOL(b); return; } 124 | #define RETURN_NULL() { RETVAL_NULL(); return;} 125 | #define RETURN_LONG(l) { RETVAL_LONG(l); return; } 126 | #define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; } 127 | #define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; } 128 | #define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; } 129 | #define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; } 130 | #define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; } 131 | #define RETURN_FALSE { RETVAL_FALSE; return; } 132 | #define RETURN_TRUE { RETVAL_TRUE; return; } 133 | 134 | ```` 135 | 其实,除了这些标量类型,还有很多php语言中的复合类型我们需要在函数中返回,如数组和对象,我们可以通过RETVAL_ZVAL与RETURN_ZVAL来操作它们,有关它们的详细介绍我们将在后续章节中叙述。 136 | ### 不返回值可以么? 137 | 其实,zend internal function的形参中还有一个比较常用的名为return_value_used的参数,它是干嘛使的呢?它用来标志这个函数的返回值在用户端有没有用到。看下面的代码: 138 | ````php 139 | 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) 202 | ZEND_FUNCTION(return_by_ref) 203 | { 204 | zval **a_ptr; 205 | zval *a; 206 | 207 | //检查全局作用域中是否有$a这个变量,如果没有则添加一个 208 | //在内核中真的是可以胡作非为啊,:-) 209 | if(zend_hash_find(&EG(symbol_table) , "a",sizeof("a"),(void **)&a_ptr ) == SUCCESS ) 210 | { 211 | a = *a_ptr; 212 | } 213 | else 214 | { 215 | ALLOC_INIT_ZVAL(a); 216 | zend_hash_add(&EG(symbol_table), "a", sizeof("a"), &a,sizeof(zval*), NULL); 217 | } 218 | 219 | //废弃return_value,使用return_value_ptr来接替它的工作 220 | zval_ptr_dtor(return_value_ptr); 221 | if( !a->is_ref__gc && a->refcount__gc > 1 ) 222 | { 223 | zval *tmp; 224 | MAKE_STD_ZVAL(tmp); 225 | *tmp = *a; 226 | zval_copy_ctor(tmp); 227 | tmp->is_ref__gc = 0; 228 | tmp->refcount__gc = 1; 229 | zend_hash_update(&EG(symbol_table), "a", sizeof("a"), &tmp,sizeof(zval*), NULL); 230 | a = tmp; 231 | } 232 | a->is_ref__gc = 1; 233 | a->refcount__gc++; 234 | *return_value_ptr = a; 235 | } 236 | #endif /* PHP >= 5.1.0 */ 237 | 238 | ```` 239 | 240 | return_value_ptr是定义zend internal function时的另外一个重要参数,他是一个zval**类型的指针,并且指向函数的返回值。我们调用zval_ptr_dtor()函数后,默认的return_value便被废弃了。这里的$a变量如果是与某个非引用形式的变量共用一个zval的话,便要进行分离。 241 | 不幸的是,如果你编译上面的代码,使用的时候便会得到一个段错误。为了使它能够正常的工作,需要在源文件中加一些东西: 242 | ````c 243 | #if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) 244 | ZEND_BEGIN_ARG_INFO_EX(return_by_ref_arginfo, 0, 1, 0) 245 | ZEND_END_ARG_INFO () 246 | #endif /* PHP >= 5.1.0 */ 247 | 248 | 然后使用下面的代码来申明我们的定义的函数: 249 | #if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) 250 | ZEND_FE(return_by_ref, return_by_ref_arginfo) 251 | #endif /* PHP >= 5.1.0 */ 252 | 253 | ```` 254 | arginfo是一种特殊的结构体,用来提前向内核告知此函数具有的一些特定的性质,如本例,其将告诉内核本函数需要引用形式的返回值,所以内核不再通过return_value来获取执行结果,而是通过return_value_ptr。如果没有arginfo,那内核会预先把return_value_ptr置为NULL,当我们对其调用zval_ptr_dtor()函数时便会使程序崩溃。 255 |
这一些代码都包含在了一个宏里面,只有在php版本大于等于5.1的时候才会被启用。如果没有这些if、endif,那我们的程序将无法在php4下通过编译,在php5.0上也会激活一些无法预测的错误。
256 |
257 |
    258 |
  • zone(zqx10104#163.com)于2011-10-20提供了一个Bug,:-)
  • 259 |
260 | 261 | 262 | ## links 263 | * 6 [函数返回值](<6.md>) 264 | * 6.2 [引用与函数的执行结果](<6.2.md>) 265 | 266 | -------------------------------------------------------------------------------- /6.2.md: -------------------------------------------------------------------------------- 1 | # 6.2 函数返回值 2 | 3 | 一个函数的执行结果要返回给调用者,除了使用return功能,还有一种办法,那就是以引用的形式传递参数,然后在内部修改这个参数的值。前一种方法往往只能返回一个值,如果我们的函数执行结果具有多种数据,便需要把这些数据打包到一个数组、类等复合类型的变量中才能得以实现;但后一种方法相比而言就简单一些了。 4 | ### 运行时传递引用:Call-time Pass-by-ref 5 | 标题有点绕口,其实很简单,功能如以下php代码所示: 6 | ````php 7 | is_ref__gc) 34 | { 35 | return; 36 | } 37 | 38 | //将a转成字符串 39 | convert_to_string(a); 40 | 41 | //更改数据 42 | ZVAL_STRING(a," (modified by ref!)",1); 43 | return; 44 | } 45 | 46 | ```` 47 | ### 编译时的传递引用Compile-time Pass-by-ref 48 | 如果每一次都在调用函数时候都对参数加一个&符号真是太罗嗦了,有没有一个简单的办法呢,比如在定义函数的时候便声明这个参数是引用形式的,而不用用户自己加&符号表示引用,而由内核来完成这步操作?这个功能是有的,我们在PHP语言中可以这样实现。 49 | ````php 50 | 原书中此处有arginfo在PHP4里的实现,被我略去了。 71 | 在Zend Engine 2 (PHP5+)中,arginfo的数据是由多个zend_arg_info结构体构成的数组,数组的每一个成员即每一个zend_arg_info结构体处理函数的一个参数。zend_arg_info结构体的定义如下: 72 | ````c 73 | typedef struct _zend_arg_info { 74 | const char *name; /* 参数的名称*/ 75 | zend_uint name_len; /* 参数名称的长度*/ 76 | const char *class_name; /* 类名 */ 77 | zend_uint class_name_len; /* 类名长度*/ 78 | zend_bool array_type_hint; /* 数组类型提示 */ 79 | zend_bool allow_null; /* 是否允许为NULL */ 80 | zend_bool pass_by_reference; /* 是否引用传递 */ 81 | zend_bool return_reference; /* 返回值是否为引用形式 */ 82 | int required_num_args; /* 必要参数的数量 */ 83 | } zend_arg_info; 84 | 85 | ```` 86 | 生成zend_arg_info结构的数组比较繁琐,为了方便PHP扩展开发者,内核已经准备好了相应的宏来专门处理此问题,首先先用一个宏函数来生成头部,然后用第二个宏生成具体的数据,最后用一个宏生成尾部代码。 87 | ````c 88 | #define ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference) ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, ZEND_RETURN_VALUE, -1) 89 | #define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \ 90 | static const zend_arg_info name[] = { \ 91 | { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args }, 92 | 93 | 94 | #define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, 0, pass_by_ref, 0, 0 }, 95 | #define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, 0, pass_by_ref, 0, 0 }, 96 | #define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, 0, allow_null, pass_by_ref, 0, 0 }, 97 | #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, 1, allow_null, pass_by_ref, 0, 0 }, 98 | 99 | 100 | #define ZEND_END_ARG_INFO() }; 101 | 102 | //这里我们先看 103 | ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference) 104 | ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,required_num_args) 105 | 106 | ```` 107 | 这两个宏函数的前两个参数的含义是一样的,name便是这个zend_arg_info数组变量的名字,这里我们定义它为:byref_compiletime_arginfo。pass_rest_by_reference如果被赋值为1,则代表着所有的参数默认都是需要以引用的方式传递的(在arginfo中单独声明的除外)。而对于ZEND_BEGIN_ARG_INFO_EX的后两个参数: 108 |
    109 |
  • name和pass_rest_by_reference的含义同上。
  • 110 |
  • return_reference:声明这个函数的返回值需要以引用的形式返回,这个参数已经在前面章节用过了。
  • 111 |
  • required_num_args:函数被调用时,传递参数至少为前N个函数(也就是后面参数都有默认值),当设置为-1时,必须传递所有参数
  • 112 |
113 | 接下来让我们看生成具体数据的宏: 114 | ````c 115 | ZEND_ARG_PASS_INFO(by_ref) 116 | //强制所有参数使用引用的方式传递 117 | 118 | 119 | ZEND_ARG_INFO(by_ref, name) 120 | //如果by_ref为1,则名称为name的参数必须以引用的方式传递, 121 | 122 | 123 | ZEND_ARG_ARRAY_INFO(by_ref, name, allow_null) 124 | ZEND_ARG_OBJ_INFO(by_ref, name, classname, allow_null) 125 | 这两个宏实现了类型绑定,也就是说我们在传递某个参数时,必须是数组类型或者某个类的实例。如果最后的参数为真,则除了绑定的数据类型,还可以传递一个NULL数据。 126 | 127 | //我们组合起来使用: 128 | ZEND_BEGIN_ARG_INFO(byref_compiletime_arginfo, 0) 129 | ZEND_ARG_PASS_INFO(1) 130 | ZEND_END_ARG_INFO() 131 | 132 | ```` 133 | 为了使我们的扩展能够兼容PHP4,还需要使用#ifdef进行特殊处理。 134 | ````c 135 | #ifdef ZEND_ENGINE_2 136 | ZEND_BEGIN_ARG_INFO(byref_compiletime_arginfo, 0) 137 | ZEND_ARG_PASS_INFO(1) 138 | ZEND_END_ARG_INFO() 139 | #else /* ZE 1 */ 140 | static unsigned char byref_compiletime_arginfo[] = { 1, BYREF_FORCE }; 141 | #endif 142 | 143 | ```` 144 | 我们copy一份ZEND_FUNCTION(byref_calltime)的实现,并重名成ZEND_FUNCTION(byref_compiletime)就行了。或者直接弄个ZEND_FALIAS就行了: 145 | ````c 146 | ZEND_FALIAS(byref_compiletime,byref_calltime,byref_compiletime_arginfo) 147 | 148 | ```` 149 | 150 | 151 | ## links 152 | * 6.1 [一个特殊的参数:return_value](<6.1.md>) 153 | * 6.3 [第六章小结](<6.3.md>) 154 | 155 | -------------------------------------------------------------------------------- /6.3.md: -------------------------------------------------------------------------------- 1 | # 6.3 函数返回值 2 | 3 | 4 | 在这一章里,我们集中讨论了如何把函数执行的结果返回给调用者,通过return语句、引用返回、通过参数返回等等,而且还初步了解了一下zend_arg_info。在下面的章节中,我们将去看一下内核是如何接收调用者传递的参数的。 5 | 6 | 7 | ## links 8 | * 6.2 [引用与函数的执行结果](<6.2.md>) 9 | * 7 [函数的参数](<7.md>) 10 | 11 | -------------------------------------------------------------------------------- /6.md: -------------------------------------------------------------------------------- 1 | # 6 函数返回值 2 | 3 | 本章目录 4 | 5 | * [1. 一个特殊的参数:return_value](6.1.md) 6 | * [2. 引用与函数的执行结果](6.2.md) 7 | * [3. 小结](6.3.md) 8 | 9 | PHP语言中函数的返回值是通过return来完成的,就像下面的程序: 10 | ````php 11 | ) 34 | * 6.1 [一个特殊的参数:return_value](<6.1.md>) 35 | 36 | -------------------------------------------------------------------------------- /7.2.md: -------------------------------------------------------------------------------- 1 | # 7.2 函数的参数 2 | 3 | 在前面的章节中我们已经介绍过arg info了,下面我们看一下如何通过其实现类型绑定,但这个特性只能在Zend Engine 2以上的版本中使用也就是PHP5以上的版本中使用。 4 | 让我们再回顾一下ZE2's argument info结构。每一个arg info结构的声明都是通过ZEND_BEGIN_ARG_INFO()或者ZEND_BEGIN_ARG_INFO_EX()宏函数开始的,然后紧跟着几行ZEND_ARG_*INFO()宏函数,最终以ZEND_END_ARG_INFO()宏函数结束。每个宏的基本作用我们可以在第6章的最后一节看到。如果我们想重写一下PHP语言中的count()函数,可以: 5 | ````c 6 | ZEND_FUNCTION(sample_count_array) 7 | { 8 | zval *arr; 9 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a",&arr) == FAILURE) 10 | { 11 | RETURN_NULL(); 12 | } 13 | RETURN_LONG(zend_hash_num_elements(Z_ARRVAL_P(arr))); 14 | } 15 | 16 | ```` 17 | zend_parse_parameters()本身可以保证传递过来的参数是一个数组。但是如果我们通过zend_get_parameter()函数来接收参数的话就没这么幸运了,需要我们自己进行类型校对。如果想让内核自动完成类型校对,便需要arg_info上场了: 18 | ````c 19 | ZEND_BEGIN_ARG_INFO(php_sample_array_arginfo, 0) 20 | ZEND_ARG_ARRAY_INFO(0, arr, 0) 21 | ZEND_END_ARG_INFO() 22 | 23 | .... 24 | PHP_FE(sample_count_array, php_sample_array_arginfo) 25 | .... 26 | 27 | ```` 28 | 29 | 这样我们便把类型校对的工作交给了Zend Engine,是不是有种如释重负的感觉!You've also given your argument a name so that the generated error messages can be more meaningful to script writers attempting to use your API. 30 | 我们同样可以对参数中的对象进行校验,限制其是继承自某个类或者实现了某个接口等等。 31 | ````c 32 | ZEND_BEGIN_ARG_INFO(php_sample_class_arginfo, 0) 33 | ZEND_ARG_OBJ_INFO(1, obj, stdClass, 0) 34 | ZEND_END_ARG_INFO() 35 | 36 | ```` 37 | 需要注意的是,此时第一个参数的值是数字1,代表着以引用的方式传递。其实这个参数对于对象来说几乎没用,因为ZE2中所有的对象在当作函数参数的时候都是默认以引用的形式传递的。但是我们又必须把这个参数设置为数字1,除非你不想让你的扩展与PHP4兼容。在PHP4中,对象是传递的一个完整Copy,而非通过引用。 38 | 39 | 对于数组和对象参数,不要忘记最后的允许为NULL的参数。更多的信息请参考第6章最后一节的有关叙述。 40 | 通过arg info的方式来实现类型绑定的功能只对ZE2及以上版本有效有效,也就是PHP5+。如果你想在PHP4上实现相应的功能,那需要用 zend_get_parameters()函数来接收参数,然后通过Z_TYPE_P()宏函数来检测参数的类型或者通过convert_to_type()函数进行类型转换。 41 | 42 | ## links 43 | * 7.1 [zend_parse_parameters](<7.1.md>) 44 | * 7.3 [php7新添加的解析函数的参数的方法 ](<7.3.md>) 45 | 46 | -------------------------------------------------------------------------------- /7.4.md: -------------------------------------------------------------------------------- 1 | # 7.4 函数的参数 2 | 3 | 现在我们已经可以编写一个更真实的函数了,既可以接收用户传递过来的参数,也可以返回数据给调用者。为了写出高质量的代码,还需要我们多花点心思在zval的写时复制等特殊机制上,否则便会在接收参数和返回数据时留下一些bug。 4 | 下面的章节里,让我们去看一下PHP语言里强大的数组类型是如何在内核中实现的,去探究内核中的HashTable结构,从而能编写出更强大的PHP扩展。 5 | 6 | 7 | 8 | 9 | ## links 10 | * 7.3 [php7新添加的解析函数的参数的方法 ](<7.3.md>) 11 | * 8 [使用HashTable与{数组}](<8.md>) 12 | 13 | -------------------------------------------------------------------------------- /7.md: -------------------------------------------------------------------------------- 1 | # 7 函数的参数 2 | 3 | * [1. zend_parse_parameters](7.1.md) 4 | * [2. Arg Info 与类型绑定](7.2.md) 5 | * [3. 小结](7.3.md) 6 | 7 | 前面的章节我们look了一下如何在扩展中定义函数,它们的实现大都比较简单,但是在实际工作中,肯定会碰到函数接收参数的问题,而它就是我们这一章要讲解的内容。 8 | 9 | 10 | 11 | 12 | ## links 13 | * 6.3 [第六章小结](<6.3.md>) 14 | * 7.1 [zend_parse_parameters](<7.1.md>) 15 | 16 | -------------------------------------------------------------------------------- /8.1.md: -------------------------------------------------------------------------------- 1 | # 8.1 使用HashTable与{数组} 2 | 3 | 我们在评选各种数据结构时,往往会考虑我们需要处理的数据规模以及需要的性能。下面让我们简要的看一下看C语言中数组和链表的一些事情。 4 | 5 | ### 数组 6 | 7 | 作者这里用的不是Array,而是Vector,可能指的是C++里的Vector, 8 | 它与数组几乎是完全一样的,唯一的不同便是可以实现动态存储。 9 | 本节下文都是用数组一词代替之,请各位注意。数组是内存中一块连续的区域,其每一个元素都具有一个唯一的下标值。 10 | 11 | ````c 12 | int a[3]; 13 | a[0]=1; 14 | a[2]=3; 15 | 16 | ```` 17 | 18 | 不仅是整数,其它类型的变量也可以保存在数组中,比如我们上一章用到的zend_get_parameters_array_ex(),便把很多zval**类型的变量保存到一个数组里,为了使其正常工作,我们提前向系统申请了相应大小的内存空间。 19 | 20 | ````c 21 | zval ***args = safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval**), 0); 22 | 23 | ```` 24 | 25 | 这里我们仍然可以用一个整数来当作下标去数组中取出我们想要的数据,就像var_dump()的实现中通过args[i]来获取参数并把它传递给php_var_dump()函数那样。 26 | 使用数组最大的好处便是速度!读写都可以在O(1)内完成,因为它每个元素的大小都是一致的,只要知道下标,便可以瞬间计算出其对应的元素在内存中的位置,从而直接取出或者写入。 27 | 28 | ### 链表 29 | 30 | 链表也是一种经常被使用的一种数据结构。链表中的每一个元素都至少有两个元素,一个指向它的下一个元素,一个用来存放它自己的数据,就像下面定义的那样: 31 | 32 | ````c 33 | typedef struct _namelist namelist; 34 | struct _namelist 35 | { 36 | struct _namelist *next; 37 | char *name; 38 | }; 39 | 40 | ```` 41 | 42 | 我们可以声明一个其类型的元素: 43 | ````c 44 | static namelist *people; 45 | ```` 46 | 47 | 假设每一个元素都代表一个人,元素中的name属性便是这个人的名字,我们通过这样的语句来得到它:people->name; 第二个属性指向后面的一个元素,那我们便可以这样来访问下一个人的名字:people->next->name, 或者下一个人的下一个人的名字:people->next->next->name,一次类推,直到next的值是NULL,代表结束。 48 | 49 | ````c 50 | //通过一个循环来遍历这个链表中的所有人~ 51 | void name_show(namelist *p) 52 | { 53 | while (p) 54 | { 55 | printf("Name: %s\n", p->name); 56 | p = p->next; 57 | } 58 | } 59 | 60 | ```` 61 | 62 | 链表可以被用来实现FIFO模式,达到先进者先出的目的! 63 | 64 | ````c 65 | static namelist *people = NULL, *last_person = NULL; 66 | void name_add(namelist *person) 67 | { 68 | person->next = NULL; 69 | if (!last_person) { 70 | /* No one in the list yet */ 71 | people = last_person = person; 72 | return; 73 | } 74 | /* Append new person to the end of the list */ 75 | last_person->next = person; 76 | 77 | /* Update the list tail */ 78 | last_person = person; 79 | } 80 | namelist *name_pop(void) 81 | { 82 | namelist *first_person = people; 83 | if (people) { 84 | people = people->next; 85 | } 86 | return first_person; 87 | } 88 | 89 | ```` 90 | 这样,我们便可以随意的向这个链表中添加或者删除数据,而不像数组那样,谨慎的考虑是否越界等问题。 91 | 上面实现的结构的学名叫做单向链表,也有地方叫单链表,反正是比较简单的意思~。它有一个致命的缺点,就是我们在插入或者读取某条数据的时候,都需要从这个链表的开始,一个个元素的向下寻找,直到找到这个元素为止。如果链表中的元素比较多,那它很容易成为我们程序中的CPU消耗大户,进而引起性能问题。为了解决这个问题,先人们发明了双向链表: 92 | 93 | ````c 94 | typedef struct _namelist namelist; 95 | struct 96 | { 97 | namelist *next, *prev; 98 | char *name; 99 | } _namelist; 100 | 101 | ```` 102 | 103 | 改动其实不大,就是在每个元素中都添加了一个prev属性,用来指向它的上一个元素。 104 | 105 | ````c 106 | void name_add(namelist *person) 107 | { 108 | person->next = NULL; 109 | if (!last_person) 110 | { 111 | /* No one in the list yet */ 112 | people = last_person = person; 113 | person->prev = NULL; 114 | return; 115 | } 116 | /* Append new person to the end of the list */ 117 | last_person ->next = person; 118 | person->prev = last_person; 119 | 120 | /* Update the list tail */ 121 | last_person = person; 122 | } 123 | 124 | ```` 125 | 126 | 单单通过上面的程序你还体会不到它的好处,但是设想一下,如果现在你有这个链表中其中一个元素的地址,并且想把它从链表中删除,那我们该怎么做呢?如果是单向链表的话,我们只能这样做: 127 | 128 | ````c 129 | void name_remove(namelist *person) 130 | { 131 | namelist *p; 132 | if (person == people) { 133 | /* Happens to be the first person in the list */ 134 | people = person->next; 135 | if (last_person == person) { 136 | /* Also happens to be the last person */ 137 | last_person = NULL; 138 | } 139 | return; 140 | } 141 | /* Search for prior person */ 142 | p = people; 143 | while (p) { 144 | if (p->next == person) { 145 | /* unlink */ 146 | p->next = person->next; 147 | if (last_person == person) { 148 | /* This was the last element */ 149 | last_person = p; 150 | } 151 | return; 152 | } 153 | p = p->next; 154 | } 155 | /* Not found in list */ 156 | } 157 | 158 | ```` 159 | 160 | 现在让我们来看看双向链表是怎样来处理这个问题的: 161 | 162 | ````c 163 | void name_remove(namelist *person) 164 | { 165 | if (people == person) { 166 | people = person->next; 167 | } 168 | if (last_person == person) { 169 | last_person = person->prev; 170 | } 171 | if (person->prev) { 172 | 173 | person->prev->next = person->next; 174 | } 175 | if (person->next) { 176 | person->next->prev = person->prev; 177 | } 178 | } 179 | 180 | ```` 181 | 对元素的遍历查找不见了,取而代之的是一个O(1)的运算,这将极大的提升我们程序的性能。 182 | 183 | ### 王者归来:HashTable才是我们的银弹! 184 | 也许你已经非常喜欢使用数组或者链表了,但我还是要向你推荐一种威力极大的数据结构,有了它之后,你可能会立即抛弃前两者,它就是HashTable. 185 | HashTable既具有双向链表的优点,同时具有能与数据匹敌的操作性能,这个数据结构几乎是PHP内核实现的基础,我们在内核代码的任何地方都发现它的痕迹。 186 | 187 | 第二章我们接触过,所有的用户端定义的变量保存在一个符号表里,而这个符号表其实就是一个HashTable,它的每一个元素都是一个zval*类型的变量。不仅如此,保存用户定义的函数、类、资源等的容器都是以HashTable的形式在内核中实现的。 188 | Zend Engine中HashTable的元素其实是指针,对其的这个改进使得HashTable能够包容各种类型的数据,从小小的标量,到复杂的PHP5中实现的类等复合数据。本章接下来的内容,我们将详细的研究如何使用zend内置的API来操作HashTable这个数据结构。 189 | 190 | 191 | ## links 192 | * 8 [使用HashTable与{数组}](<8.md>) 193 | * 8.2 [操作HashTable的API](<8.2.md>) 194 | 195 | -------------------------------------------------------------------------------- /8.4.md: -------------------------------------------------------------------------------- 1 | # 8.4 使用HashTable与{数组} 2 | 3 | 4 | 5 | 我们用了很长的篇幅在这一章描述内核中的HashTable结构以及PHP中的数组实现。在接下来的时间中,我们会在它的基础上学习一下内核是怎样实现与管理PHP语言中的资源与类的。 6 | 7 | 8 | ## links 9 | * 8.3 [在内核中操作PHP语言中数组](<8.3.md>) 10 | * 9 [PHP中的资源类型](<9.md>) 11 | 12 | -------------------------------------------------------------------------------- /8.md: -------------------------------------------------------------------------------- 1 | # 8 使用HashTable与{数组} 2 | 3 | * [1. 数组(C中的)与链表](8.1.md) 4 | * [2. 操作HashTable的API](8.2.md) 5 | * [3. 在内核中操作PHP语言中数组](8.3.md) 6 | * [4. 小结](8.4.md) 7 | 8 | 在C语言中,我们可以自定义各种各样的数据结构,用来把很多数据保存在一个变量里面,但是每种数据结构都有自己的优缺点,PHP内核规模如此庞大,是否已经找到了一些非常棒的解决方法呢? 9 | 10 | 11 | ## links 12 | * 7.3 [第七章小结](<7.3.md>) 13 | * 8.1 [数组(C中的)与链表](<8.1.md>) 14 | 15 | -------------------------------------------------------------------------------- /9.3.md: -------------------------------------------------------------------------------- 1 | # 9.3 PHP中的资源类型 2 | (TBD) 3 | 4 | zval通过引用计数来节省内存的,这个我们都知道了,但你可能不知道的是,某个zval对应的{资源}在实现时也使用了引用计数这种概念,也就是有了两种引用计数! 5 | 6 | {资源}对应的zval的类型是IS_RESOURCE,它并不保存最终的数据,而只保存一个数字,即EG(regular_list)中的数字索引。 7 | 8 | 9 | 当{资源}被创建时,比如我们调用sample_fopen()函数: 10 | ````php 11 | $a = sample_fopen('notes.txt', 'r'); 12 | //此时:var->refcount__gc = 1, rsrc->refcount = 1 13 | 14 | $b = $a; 15 | //此时:var->refcount__gc = 2, rsrc->refcount = 1 16 | 17 | unset($b); 18 | //此时:var->refcount__gc = 1, rsrc->refcount = 1 19 | 20 | /* 21 | 下面来个复杂的! 22 | */ 23 | 24 | $b = $a; 25 | $c = &$a; 26 | //此时: 27 | /* 28 | bvar->refcount = 1, bvar->is_ref = 0 29 | acvar->refcount = 2, acvar->is_ref = 1 30 | rsrc->refcount = 2 31 | */ 32 | 33 | ```` 34 | 35 | 现在,如果我们unset($b),内核只会把rsrc->refcount的值减1。只有当rsrc->refcount的值为0时,我们预设的dtor释放函数才会被激活并调用。 36 | 37 | 38 | 39 | ## links 40 | * 9.2 [Persistent Resources](<9.2.md>) 41 | * 9.4 [第九章小结](<9.4.md>) 42 | 43 | -------------------------------------------------------------------------------- /9.4.md: -------------------------------------------------------------------------------- 1 | # 9.4 PHP中的资源类型 2 | 3 | 通过这一章介绍的技术,我们已经可以使用PHP中{资源}了,这将使我们更容易的在扩展中使用一些第三方库,比如使用zip扩展时,我们需要把它的一些特殊的量封装成资源供脚本使用。这无疑极大的增强了PHP的威力!{资源}V5!

4 | 下一章将会说一下PHP中的对象!原书中分别讲述了PHP4与PHP5的实现,这里我没有参照原书的安排,按照自己的理解完全重写了这一部分,以PHP5为基础讲述,确切的说,是以PHP5.3.6讲述,这个版本问题我也已经在本书开头说明了,因为我私自认为PHP4已经不再重要了,其中又针对PHP7做了一些修改。 5 | 6 | 7 | ## links 8 | * 9.3 [{资源}自有的引用计数](<9.3.md>) 9 | * 10 [PHP中的面向对象(一)](<10.md>) 10 | 11 | -------------------------------------------------------------------------------- /9.md: -------------------------------------------------------------------------------- 1 | # 9 PHP中的资源类型 2 | 3 | * [1. 复合类型的数据——{资源}](9.1.md) 4 | * [2. Persistent Resources](9.2.md) 5 | * [3. {资源}自有的引用计数](9.3.md) 6 | * [4. 小结](9.4.md) 7 | 8 | 截止到现在,我们已经熟悉了PHP语言中的字符串、数字、布尔以及数组的数据类型了,接下来,我们将接触另外一种PHP独特的数据类型——资源(Resource)。 9 | 10 | 11 | 12 | 13 | ## links 14 | * 8.4 [第8章小结](<8.4.md>) 15 | * 9.1 [复合类型的数据——{资源}](<9.1.md>) 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 《PHP扩展开发及内核应用》 2 | 11年年初因兴趣建立此项目,有幸得到大家关注,万分荣幸。现在把他迁移到github上,以方便更多人参与进来。 3 | 项目阅读地址:[http://www.walu.cc/phpbook]() 4 | 5 | ## 撰写方法 6 | ### 认领内容 7 | 首先, 请在issues中建立一个新的issue, 说明你认领的内容, 和预计完成时间. 然后就可以Fork本项目进行编辑, 等你完成后, 发起一个Pull Request即可. 8 | ### 文件命名 9 | 每个章节建立一个md文件,如第11章的第3节,则建立**11.3.md**。 10 | 11 | ## 格式规范 12 | 请参看已有章节的规范, 要注意的是, 每个章节在底部都需要有一个links节, 包含"目录", "上一节", "下一节"的链接 13 | 14 | ## 编译方法 15 | 定期同步到phpbook 16 | 17 | ## 开始阅读 18 | [开始阅读]() 19 | -------------------------------------------------------------------------------- /contributor.md: -------------------------------------------------------------------------------- 1 | # 《PHP扩展开发与内核应用》贡献者名单 2 | 3 | 根据首次参与时间,升序排列: 4 | 5 | * Walu, [weibo.com/walu](),项目发起人。 6 | * Laruence, [weibo.com/laruence](),[Blog](),PHP开发组成员, Yaf, Taint, APC等Pecl扩展作者、维护者。鸟哥,不知道的话你就out了... 7 | * Demon,[weibo.com/409238807](),[Blog]() 8 | * 花生,[weibo.com/723441938](),[Blog]() 9 | * Guoguo,[github.com/goosman-lei](),14-20章内容提供者。 10 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | # PHP扩展开发及内核应用 2 | 3 | [**开始阅读**]() 4 | 5 | ## 介绍 6 | 7 | 虽然用了书名号,但它是我的一个业余项目而已,它以Sara Golemon在2005年著作的《Extending and Embedding PHP》一书为蓝本翻译修改而来。这里先对Sara女士表示感谢,为我们奉献了这么优秀的一本技术图书。截止到目前(2011年),这几年以来,PHP的应用在中国突飞猛进,已经渗透到了互联网的各个方面,现在每个公司里都不可能一点没有PHP的影子了。有关PHP语言自身的书籍也是层出不穷,而有关PHP扩展开发以及PHP内核方面的文字却都比较零散,比较系统的有TIPI项目、Zend上面的几篇文章以及《Extending and Embedding PHP》这本书的英文版,不能不说这直接限制住了部分人在PHP语言上的深入研究与学习。我在学校的时候就翻阅过这本书的电子版,但因为是英文的终究没有在那时深入研究下去,也算是一件憾事。 8 | 浏览本项目,希望你已经具备以下技能: 9 | 10 | * 比较熟悉PHP语言。熟悉基本的C语言 11 | * 我希望你能在Linux上来实践这个项目里的东西,那会比较容易一些,当然win也没关系。 12 | 13 | 本项目不是一个翻译工作,而是以翻译为起点的一个系统的、持续的跟踪介绍PHP内核相关知识的系统,相对于原书来讲,本项目的内容有以下不同 14 | 15 | * 基准PHP版本由5.1改为了5.3.6,也就是说本书的例子默认都是以PHP5.3.6为例的。**记录的是2011年初次编辑的时候** 16 | * 改写了大部分例子,方便像我一样的初学者。 17 | * 会根据PHP的发展与自身的进步不断添加新的内容、优化原有内容。 18 | 19 | 现在项目的第一期的工作(初译)已经完成了,正在进入后续发布工作,每校正完一节就发布一节,期间如果朋友你发现了错误,还请帮忙斧正,我将在项目日志里声明以示感谢。此外还希望你能持续关注本项目,让我们一起为中国PHP事业的发展奉献一份力量。这个项目每一次的修正日志都将以以下方式公布,还请大家予以关注: 20 | 21 | * 微博: [http://weibo.com/walu](http://weibo.com/walu) 22 | 23 | 本人不才,这个项目刚刚起步,我就已经碰到了很多的困难,期间得到了许多帮助,这里记录下来,以示感谢: 24 | 25 | * 感谢Sara在2005年创作了[本书](http://www.amazon.com/Extending-Embedding-PHP-Sara-Golemon/dp/067232704X)。 26 | * 感谢小胖姜。 27 | * 感谢laruence大牛与你的[博客](http://www.laruence.com)。 28 | * 感谢TIPI项目, Google, Baidu。 29 | * .... 30 | 31 | 32 | ##《PHP扩展开发与内核应用》贡献者名单 33 | 34 | 根据首次参与时间,升序排列: 35 | 36 | * Walu, [weibo.com/walu](),[site](),项目发起人。 37 | * Laruence, [weibo.com/laruence](),[Blog](),PHP开发组成员, Yaf, Taint, APC等Pecl扩展作者、维护者。鸟哥,不知道的话你就out了... 38 | * Demon,[weibo.com/409238807](),[Blog](),12章内容贡献者。 39 | * 花生,[weibo.com/wenjuncool](),校正&优化。 40 | * Guoguo,[blog@csdn](),14-20章内容贡献者。 41 | 42 | ## links **点击下面的目录开始阅读** 43 | * [目录]() 44 | -------------------------------------------------------------------------------- /preface.md: -------------------------------------------------------------------------------- 1 | # 《PHP扩展开发及内核应用》目录 2 | 3 | > 上一节: [《PHP扩展开发及内核应用》]() 4 | > 下一节: [PHP的生命周期](<1.md>) 5 | 6 | 目录中汉字部分代表已经翻译完成的章节,带链接的表示已经发布的,未待链接的表示正在校正即将发布的。该版本是对walu.cc的php5版本进行的升级而来,感谢walu.cc的工作。 7 | 8 | 1. [PHP的生命周期](1.md) 9 | 1. [让我们从SAPI开始](1.1.md) 10 | 2. [PHP的启动与终止](1.2.md) 11 | 3. [PHP的生命周期](1.3.md) 12 | 4. [线程安全](1.4.md) 13 | 5. [小结](1.5.md) 14 | 2. [PHP变量在内核中的实现](2.md) 15 | 1. [变量的类型](2.1.md) 16 | 2. [变量的值](2.2.md) 17 | 3. [创建PHP变量](2.3.md) 18 | 4. [变量的存储方式](2.4.md) 19 | 5. [变量的检索](2.5.md) 20 | 6. [类型转换](2.6.md) 21 | 7. [小结](2.7.md) 22 | 3. [内存管理](3.md) 23 | 1. [内存管理](3.1.md) 24 | 2. [引用计数](3.2.md) 25 | 3. [总结](3.3.md) 26 | 4. [配置编译环境](4.md) 27 | 1. [编译前的准备](4.1.md) 28 | 2. [PHP编译前的config配置](4.2.md) 29 | 3. [Unix/Linux平台下的编译](4.3.md) 30 | 4. [在Win32平台上编译PHP](4.4.md) 31 | 5. [小结](4.5.md) 32 | 5. [第一个扩展](5.md) 33 | 1. [一个扩展的基本结构](5.1.md) 34 | 2. [编译我们的扩展](5.2.md) 35 | 3. [静态编译](5.3.md) 36 | 4. [编写函数](5.4.md) 37 | 5. [小结](5.5.md) 38 | 6. [函数的返回值](6.md) 39 | 1. [一个特殊的参数:return_value](6.1.md) 40 | 2. [引用与函数的执行结果](6.2.md) 41 | 3. [小结](6.3.md) 42 | 7. [函数的参数](7.md) 43 | 1. [zend_parse_parameters](7.1.md) 44 | 2. [Arg Info 与类型绑定](7.2.md) 45 | 3. [小结](7.3.md) 46 | 8. [Array与HashTable](8.md) 47 | 1. [数组(C中的)与链表](8.1.md) 48 | 2. [操作HashTable的API](8.2.md) 49 | 3. [在内核中操作PHP语言中数组](8.3.md) 50 | 3. [小结](8.4.md) 51 | 9. [PHP中的资源类型](9.md) 52 | 1. [复合类型的数据——资源](9.1.md) 53 | 2. [Persistent Resources](9.2.md) 54 | 3. [资源自有的引用计数](9.3.md) 55 | 4. [小结](9.4.md) 56 | 10. [PHP中的面向对象(一)](10.md) 57 | 1. [zend_class_entry](10.1.md) 58 | 2. [定义一个类](10.2.md) 59 | 3. [定义一个接口](10.3.md) 60 | 4. [类的继承与接口的实现](10.4.md) 61 | 5. [抛出异常|错误](10.5.md) 62 | 6. [小结](10.6.md) 63 | 64 | 11. [PHP中的面向对象(二)](11.md) 65 | 1. [生成对象的实例与调用方法](11.1.md) 66 | 2. [读写对象的属性](11.2.md) 67 | 3. [小结](11.3.md) 68 | 12. [启动与终止的那点事](12.md) 69 | 1. [关于生命周期](12.1.md) 70 | 2. [MINFO与phpinfo](12.2.md) 71 | 3. [常量](12.3.md) 72 | 4. [PHP扩展中的全局变量](12.4.md) 73 | 5. [PHP语言中的超级全局变量](12.5.md) 74 | 6. [小结](12.6.md) 75 | 13. [ini配置文件](<13.md>) 76 | 1. [读写ini配置](<13.1.md>) 77 | 2. [小结](13.2.md) 78 | 14. [流式访问](<14.md>) 79 | 1. [流的概览](<14.1.md>) 80 | 2. [打开与读写流](<14.3.md>) 81 | 3. [Static Stream Operations](<14.3.md>) 82 | 4. [小结](<14.4.md>) 83 | 15. [流的实现](<15.md>) 84 | 1. [PHP Streams的本质](<15.1.md>) 85 | 2. [流的封装——wrapper](<15.2.md>) 86 | 3. [实现wrapper](<15.3.md>) 87 | 4. [Manipulation](<15.4.md>) 88 | 5. [状态与属性读取](<15.5.md>) 89 | 6. [小结](<15.6.md>) 90 | 16. [有趣的流](<16.md>) 91 | 1. [流的上下文](<16.1.md>) 92 | 2. [流的过滤器](<16.2.md>) 93 | 3. [小结](<16.3.md>) 94 | 17. [配置和链接](<17.md>) 95 | 1. [Autoconf](<17.1.md>) 96 | 2. [库的查找](<17.2.md>) 97 | 3. [强制模块依赖](<17.3.md>) 98 | 4. [Speaking the Windows Dialect](<17.4.md>) 99 | 5. [小结](<17.5.md>) 100 | 18. [扩展生成器](<18.md>) 101 | 1. [ext_skel生成器](<18.1.md>) 102 | 2. [PECL_Gen生成器](<18.2.md>) 103 | 3. [小结](<18.3.md>) 104 | 19. [设置宿主环境](<19.md>) 105 | 1. [嵌入式SAPI](<19.1.md>) 106 | 2. [构建并编译一个宿主应用](<19.2.md>) 107 | 3. [通过嵌入包装重新创建cli](<19.3.md>) 108 | 4. [老技术新用](<19.4.md>) 109 | 5. [小结](<19.5.md>) 110 | 20. [高级嵌入式](<20.md>) 111 | 1. [回调到php中](<20.1.md>) 112 | 2. [错误处理](<20.2.md>) 113 | 3. [初始化php](<20.3.md>) 114 | 4. [覆写INI_SYSTEM和INI_PERDIR选项](<20.4.md>) 115 | 5. [捕获输出](<20.5.md>) 116 | 6. [同时扩展和嵌入](<20.6.md>) 117 | 7. [小结](<20.7.md>) 118 | -------------------------------------------------------------------------------- /约定... ...: -------------------------------------------------------------------------------- 1 | 1.函数的名称后面都带括号,比如:用户可以通过define()函数定义常量。 2 | 2.函数式宏定义简称为宏函数。 3 | 3.每一章最后的Summary叫做小结 4 | 5 | 几个重要的class 6 |

    代表目录 7 | --------------------------------------------------------------------------------