为什么要用C扩展
C是静态编译的,执行效率比PHP代码高很多。同样的运算代码,使用C来开发,性能会比PHP要提升数百倍。
另外C扩展是在进程启动时加载的,PHP代码只能操作Request生命周期的数据,C扩展可操作的范围更广。
下载PHP7.1.1扩展
1.下载地址: http://php.net/get/php-7.1.1.tar.bz2/from/a/mirror
2.下载后进行解压
创建扩展骨架
##本例用的是php7.1.1 cd ext ./ext_skel --extname=helloworld
修改config.m4
把下面几行注释打开,config.m4中 dnl 为注释的意思
##动态编译选项,通过.so的方式链接,去掉dnl注释: PHP_ARG_WITH(helloworld, for helloworld support, Make sure that the comment is aligned: [ --with-helloworld Include helloworld support]) ##静态编译选项,通过enable来启用,去掉dnl注释: PHP_ARG_ENABLE(helloworld, whether to enable helloworld support, Make sure that the comment is aligned: [ --enable-helloworld Enable helloworld support])
修改完成编译下
phpize #这里用自己的php-config文件位置 ./configure --with-php-config=./configure --with-php-config=/Applications/MAMP/bin/php/php7.1.1/bin/php-config make && make install make test #测试 #编辑php.ini,加上helloworld扩展 extension=helloworld.so
在myfun有个php的测试脚本,执行测试下
php -d enable_dl=On myfile.php
输出结果:
Functions available in the test extension: confirm_myfun_compiled Congratulations! You have successfully modified ext/myfun/config.m4. Module myfun is now compiled into PHP.
其实confirm_myfun_compiled是构建工具帮我们生成的测试函数
创建helloWorld函数
现在我们来创建属于自己的函数 helloWorld()
,功能就是输出 Hello World!
vim myfun/php_myfun.h
##在PHP_FUNCTION(confirm_myfun_compiled); 下追加一行 PHP_FUNCTION(helloWorld);
实现helloworld实体
vim myfun/myfun.c
##zend_function_entry myfun_functions 补充要实现的函数 const zend_function_entry myfun_functions[] = { PHP_FE(confirm_myfun_compiled, NULL) /* For testing, remove later. */ PHP_FE(helloWorld, NULL) /*这是补充的一行,末尾没有逗号*/ {NULL, NULL, NULL} /* Must be the last line in myfun_functions[] */ }; ##在末尾实现helloworld的内容 PHP_FUNCTION(helloWorld) { php_printf("Hello World!\n"); RETURN_TRUE; }
重新编译
./configure && make && make install
测试
php -d enable_dl=On -r "dl('myfun.so');helloWorld();" Hello World! php -d enable_dl=On -r "dl('myfun.so');print confirm_myfun_compiled('helloWorld');" Congratulations! You have successfully modified ext/myfun/config.m4. Module helloWorld is now compiled into PHP.
头文件分析
#ifndef PHP_MYFUN_H #define PHP_MYFUN_H extern zend_module_entry myfun_module_entry; #define phpext_myfun_ptr &myfun_module_entry #ifdef PHP_WIN32 # define PHP_MYFUN_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 # define PHP_MYFUN_API __attribute__ ((visibility("default"))) #else # define PHP_MYFUN_API #endif #ifdef ZTS #include "TSRM.h" #endif PHP_MINIT_FUNCTION(myfun); /*当PHP被装载时,模块启动函数即被引擎调用。这使得引擎做一些例如资源类型,注册INI变量等的一次初始化。*/ PHP_MSHUTDOWN_FUNCTION(myfun); /*当PHP完全关闭时,模块关闭函数即被引擎调用。通常用于注销INI条目*/ PHP_RINIT_FUNCTION(myfun); /*在每次PHP请求开始,请求前启动函数被调用。通常用于管理请求前逻辑。*/ PHP_RSHUTDOWN_FUNCTION(myfun); /*在每次PHP请求结束后,请求前关闭函数被调用。经常应用在清理请求前启动函数的逻辑。*/ PHP_MINFO_FUNCTION(myfun); /*调用phpinfo()时模块信息函数被呼叫,从而打印出模块信息。*/ PHP_FUNCTION(confirm_myfun_compiled); /* For testing, remove later. */ PHP_FUNCTION(helloWorld); #ifdef ZTS #define MYFUN_G(v) TSRMG(myfun_globals_id, zend_myfun_globals *, v) #else #define MYFUN_G(v) (myfun_globals.v) #endif #endif /* PHP_MYFUN_H */
confirm_myfun_compiled分析
PHP_FUNCTION(confirm_myfun_compiled) //使用了宏PHP_FUNCTION(),该宏可以生成一个适合于Zend引擎的函数原型 { char *arg = NULL; int arg_len, len; char *strg; //获得函数传递的参数 //第一个参数是传递给函数的参数个数。通常的做法是传给它ZEND_NUM_ARGS()。这是一个表示传递给函数参数总个数的宏。 //第二个参数是为了线程安全,总是传递TSRMLS_CC宏。 //第三个参数是一个字符串,指定了函数期望的参数类型,后面紧跟着需要随参数值更新的变量列表。因为PHP采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。例如,如果用户传递一个整数变量,可函数需要一个浮点数,那么zend_parse_parameters()就会自动地把整数转换为相应的浮点数。如果实际值无法转换成期望类型(比如整形到数组形),会触发一个警告。 /* 类型指示符 l long 符号整数 d double 浮点数 s char *, int 二进制字符串,长度 b zend_bool 逻辑型(1或0) r zval * 资源(文件指针,数据库连接等) a zval * 联合数组 o zval * 任何类型的对象 O zval * 指定类型的对象。需要提供目标对象的类类型 z zval * 无任何操作的zval */ //第四个参数为传递的参数数据的引用 //第五个参数为传递的参数个数 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) { //获得函数传递的参数 return; } len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "myfun", arg); /* 通过设置RETURN_type()的方式,将返回控制到PHP。下表解释了大多数存在的宏。 RETVAL_LONG(l) 整数 RETVAL_BOOL(b) 布尔数(1或0) RETVAL_NULL() NULL RETVAL_DOUBLE(d) 浮点数 RETVAL_STRING(s, dup) 字符串。如果dup为1,引擎会调用estrdup()重复s,使用拷贝。如果dup为0,就使用s RETVAL_STRINGL(s, l, dup) 长度为l的字符串值。与上一个宏一样,但因为s的长度被指定,所以速度更快。 RETVAL_TRUE 返回布尔值true。注意到这个宏没有括号。 RETVAL_FALSE 返回布尔值false。注意到这个宏没有括号。 RETVAL_RESOURCE(r) 资源句柄。 */ RETURN_STRINGL(strg, len, 0); }