本文主要是为了熟悉php扩展开发,开发一个农历扩展貌似并没有什么luan用。php成功的原因之一是他拥有强大的扩展,并且能够让开发者灵活的开发自己的php扩展。致力于成为一个lnmpstackexpert的我(一不小心又装b了),当然不能错过,要学习专研的知识还有很多很多!!
先定义一个函数定义文件,比thrift的IDL文件要简单的多。函数定义文件定义扩展对外提供的函数原型。一般格式是一个函数一行。函数定义需要指定返回值和传入参数的类型,可以包括:bool,float,int,array等。农历算发的函数定义文件如下:
stringdatetolunar(intyear,intmonth,intday);
当然也可以在同一个函数定义文件中定义多个函数,也可以定义一个类,这样就能够生成对应的函数类扩展骨架。比如:
classLunar{
}
在PHP的源码目录下的ext/目录下有ext_skel脚本。用它可以根据刚刚定义的农历扩展函数定义文件lunar.skel生成扩展骨架。
~#./ext_skel–extname=datetolunar–proto=lunar.skel
extname表示生成的扩展名字,执行之后就会在ext/目录下生成datetolunar目录。
tigerphp-5.6.17/ext/datetolunarls[14:44:36]CREDITSEXPERIMENTALconfig.m4config.w32datetolunar.cdatetolunar.phpphp_datetolunar.htests
为了使扩展能够被编译,需要修改其中的config.m4文件,去掉虾面几行的注释(即dnl)
PHP_ARG_WITH(datetolunar,fordatetolunarsupport,
dnlMakesurethatthecommentisaligned:
[–with-datetolunarIncludedatetolunarsupport])
dnlOtherwiseuseenable:
PHP_ARG_ENABLE(datetolunar,whethertoenabledatetolunarsupport,
[–enable-datetolunarEnabledatetolunarsupport])
如果没有引用任何外部的C库的话,就不需要过多更改了。否则可能需要了解下这些
PHP_ADD_INCLUDE
PHP_ADD_LIBRARY_WITH_PATH
PHP_SUBST
PHP_NEW_EXTENSION
具体用法google下就好。
现在就已经可以编译这个扩展到你的PHP了(虽然并没有什么功能)。我一般会选择动态编译的方式,大部分开源的PHP扩展,我也会用这种方式,因为简单。
~#phpize
~#./configure–with-php-config=/opt/php/bin/php-config
~#make
~#makeinstall
之后就会生成datetolunar.so文件,添加到php.ini中添加extension=datetolunar.so,就可以尝试调用这个扩展了。生成的骨架代码中有测试扩展的PHP文件,检查扩展是否成功编译到PHP。
tigerphp-5.6.17/ext/datetolunarphpdatetolunar.php[14:31:26]
Functionsavailableinthetestextension:
confirm_datetolunar_compiled
datetolunar
Congratulations!Youhavesuccessfullymodifiedext/datetolunar/config.m4.ModuledatetolunarisnowcompiledintoPHP.
介绍了PHP扩展的开发流程之后,我们看看如何实现一个农历扩展。我们可能需要先大概了解一下农历算法。
农历算发基础知识
–算法系列之二十:计算中国农历(一)
–算法系列之二十:计算中国农历(二)
打开datetolunar.c文件能够看到如下的hex_lunar定义。
staticinthex_lunar[]={
0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,
0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,
….
};
其中每一个16进制的数字表示是从1900年~2050年的所有农历数据信息,把16进制转换成2进制,每一部分表示如下
1-4:表示当年有无闰年,有的话,为闰月的月份,没有的话,为0。
5-16:为除了闰月外的正常月份是大月还是小月,1为30天,0为29天。
注意:从1月到12月对应的是第16位到第5位。
17-20:表示闰月是大月还是小月,仅当存在闰月的情况下有意义。
举个例子:
1980年的数据是:0x095b0
二进制:00001001010110110000
表示1980年没有闰月,从1月到12月的天数依次为:30、29、29、30、29、30、29、30、30、29、30、30。
1982年的数据是:0x0a974
000010100100101110100
表示1982年的4月为闰月,即有第二个4月,且是闰小月。
从1月到13月的天数依次为:30、29、30、29、29(闰月)、30、29、29、30、29、30、30、30。
由于刚开始学习写PHP扩展,不太熟悉。我选择先写完并且调试好功能之后,再把代码复制到PHP扩展中。C语言debug,推荐使用cgdb,能够边看代码边debug,比gdb方便很多。
编译
~#gcc-glunar.c-olunar
使用cgdb调试
~#sudocgdblunar
break–设置断点
run–运行程序
print–打印变量
continue–继续执行
PS:如果打印的数组很长,print被简写的话,可以在gdb中通过
>>setprintelement0
来设置。
C代码写完并且没问题之后,准备迁移到PHP的扩展中。在之前使用ext_skel脚本生成的骨架代码datetolunar.c中,有自动生成的C函数PHP_FUNCTION(datetolunar)的定义。
/*{{{protostringdatetolunar(intyear,intmonth,intday)
;*/
PHP_FUNCTION(datetolunar)
{
intargc=ZEND_NUM_ARGS();
longyear;
longmonth;
longday;
if(zend_parse_parameters(argcTSRMLS_CC,“lll”,&year,&month,&day)==FAILURE)return;
php_error(E_WARNING,“datetolunar:notyetimplemented”);
/*}}}*/
为了获得函数传递的参数,可以使用zend_parse_parameters()API函数。下面是该函数的原型:
zend_parse_parameters(intnum_argsTSRMLS_DC,char*type_spec,…);
第一个参数是传递给函数的参数个数。通常的做法是传给它ZEND_NUM_ARGS()。这是一个表示传递给函数参数总个数的宏。第二个参数是为了线程安全,总是传递TSRMLS_CC宏。第三个参数是一个字符串,指定了函数期望的参数类型,后面紧跟着需要随参数值更新的变量列表。因为PHP采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。例如,如果用户传递一个整数变量,可函数需要一个浮点数,那么zend_parse_parameters()就会自动地把整数转换为相应的浮点数。如果实际值无法转换成期望类型(比如整形到数组形),会触发一个警告。
了解这些之后,复制写好的C代码到函数中。需要做的只是再让PHP返回我们想要的结果就好。
这里返回一个字符串,我们用到的函数是RETURN_STRING
char*ret=lunar_output;RETVAL_STRINGL(lunar_output,strlen(ret),1);
这是直接返回了一个静态字符串,如果最后一个参数设为0,会导致php在free这个字符串的时候出错。因为PHP是类型安全的脚本语言,对于RETURN_STRINGL或是RETURN_STRING返回的字符串,php会在适当的时候free掉,所以程序员要保证返回的字符串在堆里,能够free掉,这就是为什么动态分配就没事的原因。而:
char*ret=“helloworld”;RETURN_STRINGL(ret,strlen(ret),0);
这是直接返回了一个静态字符串,导致PHP在free这个字符串的时候出错。RETURN_STRINGL和RETURN_STRING最后一个参数,如果是1,表示对第一个参数中的字符串在堆里复制一份返回,这样就没问题了。
这样就完成了我们的PHP农历扩展。编译成功!!!(当然make的时候,可能会有问题,逐一解决就好)
tigerwork/github/php-lunar-extensionphp-r“echodatetolunar(2016,2,27);”[14:47:19]丙申[猴]年正月二十%
OK,Good~
实现同样功能的农历转换代码。第一个是php实现的农历函数,第二个是我们刚刚实现的PHP扩展,都执行10000次,性能提高了大概86倍(略激动~)。
tigerwork/github/php-lunar-extensionphptest.php[18:28:06]
>>Excutephpfunction.
1.7234871387482
>>Excutephpextensionfunction.
0.022943019866943
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!