php调用C代码的方法.docx
- 文档编号:23103006
- 上传时间:2023-04-30
- 格式:DOCX
- 页数:20
- 大小:24.61KB
php调用C代码的方法.docx
《php调用C代码的方法.docx》由会员分享,可在线阅读,更多相关《php调用C代码的方法.docx(20页珍藏版)》请在冰豆网上搜索。
php调用C代码的方法
在php程序中需要用到C代码,应该是下面两种情况:
1已有C代码,在php程序中想直接用
2由于php的性能问题,需要用C来实现部分功能
针对第一种情况,最合适的方法是用system调用,把现有C代码写成一个独立的程序。
参数通过命令行或者标准输入传入,结果从标准输出读出。
其次,稍麻烦一点的方法是C代码写成一个daemon,php程序用socket来和它进行通讯。
重点讲讲第二种情况,虽然沿用system调用的方法也可以,但是想想你的目的是优化性能,那么频繁的起这么多进程,当然会让性能下降。
而写daemon的方法固然可行,可是繁琐了很多。
我的简单测试,同样一个算法,用C来写比用php效率能提高500倍。
而用php扩展的方式,也能提高90多倍(其中的性能损失在了参数传递上了吧,我猜)。
所以有些时候php扩展就是我们的最佳选择了。
这里我着重介绍一下用C写php扩展的方法,而且不需要重新编译php。
首先,找到一个php的源码,php4或者php5版本的都可以,与你目标平台的php版本没有关系。
在源码的ext目录下可以找到名为ext_skel的脚本(windows平台使用ext_skel_win32.php)
在这个目录下执行./ext_skel--extname=hello(我用hello作为例子)
这时生成了一个目录hello,目录下有几个文件,你只需要关心这三个:
config.m4hello.cphp_hello.h
把这个目录拷备到任何你希望的地方,cd进去,依次执行
(安装phpize等工具yum-yinstallphp-devel)
phpize
./configure
make
什么也没发生,对吧?
这是因为漏了一步,打开config.m4,找到下面
dnlIfyourextensionreferencessomethingexternal,usewith:
...
dnlOtherwiseuseenable:
...
这是让你选择你的扩展使用with还是enable,我们用with吧。
把with那一部分取消注释。
如果你和我一样使用vim编辑器,你就会很容易发现dnl三个字母原来是表示注释的呀(这是因为vim默认带了各种文件格式的语法着色包)
我们修改了config.m4后,继续
phpize
./configure
make
这时,modules下面会生成hello.so和hello.la文件。
一个是动态库,一个是静态库。
你的php扩展已经做好了,尽管它还没有实现你要的功能,我先说说怎么使用这个扩展吧!
ext_skel为你生成了一个hello.php里面有调用示例,但是那个例子需要你把hello.so拷贝到php的扩展目录中去,我们只想实现自己的功能,不想打造山寨版php,改用我下面的方法来加载吧:
if(!
extension_loaded("hello")){
dl_local("hello.so");
}
functiondl_local($extensionFile){
//makesurethatweareABLEtoloadlibraries
if(!
(bool)ini_get("enable_dl")||(bool)ini_get("safe_mode")){
die("dh_local():
Loadingextensionsisnotpermitted.\n");
}
//checktomakesurethefileexists
if(!
file_exists(dirname(__FILE__)."/".$extensionFile)){
die("dl_local():
File'$extensionFile'doesnotexist.\n");
}
//checkthefilepermissions
if(!
is_executable(dirname(__FILE__)."/".$extensionFile)){
die("dl_local():
File'$extensionFile'isnotexecutable.\n");
}
//wefigureoutthepath
$currentDir=dirname(__FILE__)."/";
$currentExtPath=ini_get("extension_dir");
$subDirs=preg_match_all("/\//",$currentExtPath,$matches);
unset($matches);
//letsmakesureweextractedavalidextensionpath
if(!
(bool)$subDirs){
die("dl_local():
Couldnotdetermineavalidextensionpath[extension_dir].\n");
}
$extPathLastChar=strlen($currentExtPath)-1;
if($extPathLastChar==strrpos($currentExtPath,"/")){
$subDirs--;
}
$backDirStr="";
for($i=1;$i<=$subDirs;$i++){
$backDirStr.="..";
if($i!
=$subDirs){
$backDirStr.="/";
}
}
//constructthefinalpathtoload
$finalExtPath=$backDirStr.$currentDir.$extensionFile;
//nowweexecutedl()toactuallyloadthemodule
if(!
dl($finalExtPath)){
die();
}
//ifthemodulewasloadedcorrectly,wemustbowgrabthemodulename
$loadedExtensions=get_loaded_extensions();
$thisExtName=$loadedExtensions[sizeof($loadedExtensions)-1];
//lastly,wereturntheextensionname
return$thisExtName;
}//enddl_local()
这样的好处是你的php扩展可以随你的php代码走,绿色扩展。
随后一个让人关心的问题是,如何添加函数、实现参数传递和返回值
添加函数步骤如下:
php_hello.h:
PHP_FUNCTION(confirm_hello_compiled);//括号里面填写函数名
hello.c
zend_function_entryhello_functions[]={
PHP_FE(confirm_hello_compiled,NULL)/*这里添加一行*/
{NULL,NULL,NULL}/*Mustbethelastlineinhello_functions[]*/
};
PHP_FUNCTION(confirm_hello_compiled)
{//这里写函数体
}
要实现的函数原型其实都一个样,用宏PHP_FUNCTION来包装了一下,另外呢,在hello_functions里面添加了一行信息,表示你这个模块中有这个函数了。
那么都是一样的函数原型,如何区分返回值与参数呢?
我给一个例子:
PHP_FUNCTION(hello_strdiff)
{
char*r1=NULL,*r2=NULL;
intn=0,m=0;
if(zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC,"ss",&r1,&n,&r2,&m)==FAILURE){
return;
}
while(n&&m&&*r1==*r2){
r1++;
r2++;
n--;
m--;
}
if(n==0)RETURN_LONG(m);
if(m==0)RETURN_LONG(n);
intd[n+1][m+1];
intcost;
inti,j;
for(i=0;i<=n;i++)d[i][0]=i;
for(j=0;j<=m;j++)d[0][j]=j;
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
if(r1[i-1]==r2[j-1])cost=0;
elsecost=1;
inta=MIN(d[i-1][j]+1,d[i][j-1]+1);
a=MIN(a,d[i-1][j-1]+cost);
d[i][j]=a;
}
}
RETURN_LONG(d[n][m]);
}
这是一个求两个字符串差异度的算法,输入参数两个字符串,返回整型。
参数的传递看这里
zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC,"ss",&r1,&n,&r2,&m)
把这个当成是scanf来理解好了。
类型说明见下表:
Booleanbzend_bool
Longllong
Doubleddouble
Stringschar*,int
Resourcerzval*
Arrayazval*
Objectozval*
zvalzzval*
如果想实现可选参数的话,例如一个字符串,一个浮点,再加一个可选的bool型,可以用"sd|b"来表示。
和scanf有一点不同的是,对于字符串,你要提供两个变量来存储,一个是char*,存字符串的地址,一个int,来存字符串的长度。
这样有必要的时候,你可以安全的处理二进制数据。
那么返回值怎么办呢?
使用下面一组宏来表示:
RETURN_STRING
RETURN_LONG
RETURN_DOUBLE
RETURN_BOOL
RETURN_NULL
注意RETURN_STRING有两个参数
当你需要复制一份字符串时使用
RETURN_STRING("HelloWorld",1);
否则使用
RETURN_STRING(str,0);
这里涉及到了模块中内存的分配,当你申请的内存需要php程序中去释放的话,请参照如下表
TraditionalNon-PersistentPersistent
malloc(count)
calloc(count,num)emalloc(count)
ecalloc(count,num)pemalloc(count,1)*
pecalloc(count,num,1)
strdup(str)
strndup(str,len)estrdup(str)
estrndup(str,len)pestrdup(str,1)
pemalloc()&memcpy()
free(ptr)efree(ptr)pefree(ptr,1)
realloc(ptr,newsize)erealloc(ptr,newsize)perealloc(ptr,newsize,1)
malloc(count*num+extr)**safe_emalloc(count,num,extr)safe_pemalloc(count,num,extr)
一般我们使用Non-Persistent中列出的这些好了。
基本上就是这样,可以开始写一个php的扩展了。
从我目前的应用来看,能操纵字符串就够用了,所以我就只能介绍这么多了,如果要详细一点的呢,例如php数组怎么处理,可以参考
翻译:
更好的文章:
更详细的呢,可以参考php手册中的《ZendAPI:
深入PHP内核》一章
不过这些资料都是英文的。
php扩展基础
本节没有介绍关于脚本引擎基本构造的一些知识,而是直接进入扩展的编码讲解中,因此不要担心你无法立刻获得对扩展整体把握的感觉。
假设你正在开发一个网站,需要一个把字符串重复n次的函数。
下面是用PHP写的例子:
functionself_concat($string,$n)
{
$result="";
for($i=0;$i<$n;$i++){
$result.=$string;
}
return$result;
}
self_concat("One",3)returns"OneOneOne".
self_concat("One",1)returns"One".
假设由于一些奇怪的原因,你需要时常调用这个函数,而且还要传给函数很长的字符串和大值n。
这意味着在脚本里有相当巨大的字符串连接量和内存重新分配过程,以至显著地降低脚本执行速度。
如果有一个函数能够更快地分配大量且足够的内存来存放结果字符串,然后把$string重复n次,就不需要在每次循环迭代中分配内存。
为扩展建立函数的第一步是写一个函数定义文件,该函数定义文件定义了扩展对外提供的函数原形。
该例中,定义函数只有一行函数原形self_concat():
stringself_concat(stringstr,intn)
函数定义文件的一般格式是一个函数一行。
你可以定义可选参数和使用大量的PHP类型,包括:
bool,float,int,array等。
保存为myfunctions.def文件至PHP原代码目录树下。
该是通过扩展骨架(skeleton)构造器运行函数定义文件的时机了。
该构造器脚本叫ext_skel,放在PHP原代码目录树的ext/目录下(PHP原码主目录下的README.EXT_SKEL提供了更多的信息)。
假设你把函数定义保存在一个叫做myfunctions.def的文件里,而且你希望把扩展取名为myfunctions,运行下面的命令来建立扩展骨架
./ext_skel--extname=myfunctions--proto=myfunctions.def
这个命令在ext/目录下建立了一个myfunctions/目录。
你要做的第一件事情也许就是编译该骨架,以便编写和测试实际的C代码。
编译扩展有两种方法:
☞作为一个可装载模块或者DSO(动态共享对象)
☞静态编译到PHP
因为第二种方法比较容易上手,所以本章采用静态编译。
如果你对编译可装载扩展模块感兴趣,可以阅读PHP原代码根目录下的README.SELF-CONTAINED_EXTENSIONS文件。
为了使扩展能够被编译,需要修改扩展目录ext/myfunctions/下的config.m4文件。
扩展没有包裹任何外部的C库,你需要添加支持--enable-myfunctions配置开关到PHP编译系统里(–with-extension开关用于那些需要用户指定相关C库路径的扩展)。
可以去掉自动生成的下面两行的注释来开启这个配置。
PHP_ARG_ENABLE(myfunctions,whethertoenablemyfunctionssupport,
[--enable-myfunctionsIncludemyfunctionssupport])
现在剩下的事情就是在PHP原代码树根目录下运行./buildconf,该命令会生成一个新的配置脚本。
通过查看./configure--help输出信息,可以检查新的配置选项是否被包含到配置文件中。
现在,打开你喜好的配置选项开关和--enable-myfunctions重新配置一下PHP。
最后的但不是最次要的是,用make来重新编译PHP。
ext_skel应该把两个PHP函数添加到你的扩展骨架了:
打算实现的self_concat()函数和用于检测myfunctions是否编译到PHP的confirm_myfunctions_compiled()函数。
完成PHP的扩展开发后,可以把后者去掉。
php
printconfirm_myfunctions_compiled("myextension");
?
>
运行这个脚本会出现类似下面的输出:
"Congratulations!
Youhavesuccessfullymodifiedext/myfunctions
config.m4.ModulemyfunctionsisnowcompiledintoPHP."
另外,ext_skel脚本生成一个叫myfunctions.php的脚本,你也可以利用它来验证扩展是否被成功地编译到PHP。
它会列出该扩展所支持的所有函数。
现在你学会如何编译扩展了,该是真正地研究self_concat()函数的时候了。
下面就是ext_skel脚本生成的骨架结构:
/*{{{protostringself_concat(stringstr,intn)
*/
PHP_FUNCTION(self_concat)
}
char*str=NULL;
intargc=ZEND_NUM_ARGS();
intstr_len;
longn;
if(zend_parse_parameters(argcTSRMLS_CC,"sl",&str,&str_len,&n)==FAILURE)
return;
php_error(E_WARNING,"self_concat:
notyetimplemented");
}
/*}}}*/
zend_parse_parameters详解
自动生成的PHP函数周围包含了一些注释,这些注释用于自动生成代码文档和vi、Emacs等编辑器的代码折叠。
函数自身的定义使用了宏PHP_FUNCTION(),该宏可以生成一个适合于Zend引擎的函数原型。
逻辑本身分成语义各部分,取得调用函数的参数和逻辑本身。
为了获得函数传递的参数,可以使用zend_parse_parameters()API函数。
下面是该函数的原型:
zend_parse_parameters(intnum_argsTSRMLS_DC,char*type_spec,…);
第一个参数是传递给函数的参数个数。
通常的做法是传给它ZEND_NUM_ARGS()。
(ZEND_NUM_ARGS()来表示对传入的参数“有多少要多少”)这是一个表示传递给函数参数总个数的宏。
第二个参数是为了线程安全,总是传递TSRMLS_CC宏,后面会讲到。
第三个参数是一个字符串,指定了函数期望的参数类型,后面紧跟着需要随参数值更新的变量列表。
因为PHP采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。
例如,如果用户传递一个整数变量,可函数需要一个浮点数,那么zend_parse_parameters()就会自动地把整数转换为相应的浮点数。
如果实际值无法转换成期望类型(比如整形到数组形),会触发一个警告。
下表列出了可能指定的类型。
我们从完整性考虑也列出了一些没有讨论到的类型。
类型指定符
对应的C类型
描述
l
long
符号整数
d
double
浮点数
s
char*,int
二进制字符串,长度
b
zend_bool
逻辑型(1或0)
r
zval*
资源(文件指针,数据库连接等)
a
zval*
联合数组
o
zval*
任何类型的对象
O
zval*
指定类型的对象。
需要提供目标对象的类类型
z
zval*
无任何操作的zval
为了容易地理解最后几个选项的含义,你需要知道zval是Zend引擎的值容器[1]。
无论这个变量是布尔型,字符串型或者其他任何类型,其信息总会包含在一个zval联合体中。
本章中我们不直接存取zval,而是通过一些附加的宏来操作。
下面的是或多或少在C中的zval,以便我们能更好地理解接下来的代码。
typedefunion_zval{
longlval;
doubledval;
struct{
char*val;
intlen;
}str;
HashTable*ht;
zend_object_valueobj;
}zval;
在我们的例子中,我们用基本类型调用zend_parse_parameters(),以本地C类型的方式取得函数参数的值,而不是用zval容器。
为了让zend_parse_parameters()能够改变传递给它的参数的值,并返回这个改变值,需要传递一个引用。
仔细查看一下self_concat():
if(zend_parse_parameters(argcTSRMLS_CC,"sl",&str,&str_len,&n)==FAILURE)
return;
注意到自动生成的代码会检测函数的返回值FAILUER(成功即SUCCESS)来判断是否成功。
如果没有成功则立即返回,并且由zend_parse_parameters()负责触发警告信息。
因为函数打算接收一个字符串l和一个整数n,所以指定”sl”作为其类型指示符。
s需要两个参数,所以我们传递参考char*和int(str和str_len)给zend_parse_parameters()函数。
无论什么时候,记得总是在代码中使用字符串长度str_len来确保函数工作在二进制安全的环境中。
不要使用strlen()和strcpy(),除非你不介意函数在二进制字符串下不能工作。
二进制字符串是包含有n
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- php 调用 代码 方法
![提示](https://static.bdocx.com/images/bang_tan.gif)