php调用c的方法.doc_第1页
php调用c的方法.doc_第2页
php调用c的方法.doc_第3页
php调用c的方法.doc_第4页
php调用c的方法.doc_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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.m4 hello.c php_hello.h把这个目录拷备到任何你希望的地方,cd进去,依次执行(安装phpize等工具 yum -y install php-devel )phpize./configuremake什么也没发生,对吧?这是因为漏了一步,打开config.m4,找到下面dnl If your extension references something external, use with:.dnl Otherwise use enable:.这是让你选择你的扩展使用with还是enable,我们用with吧。把with那一部分取消注释。如果你和我一样使用vim编辑器,你就会很容易发现dnl三个字母原来是表示注释的呀(这是因为vim默认带了各种文件格式的语法着色包)我们修改了config.m4后,继续phpize./configuremake这时,modules下面会生成hello.so和hello.la文件。一个是动态库,一个是静态库。你的php扩展已经做好了,尽管它还没有实现你要的功能,我先说说怎么使用这个扩展吧!ext_skel为你生成了一个hello.php里面有调用示例,但是那个例子需要你把hello.so拷贝到php的扩展目录中去,我们只想实现自己的功能,不想打造山寨版php,改用我下面的方法来加载吧:1 if(!extension_loaded(hello) 2 dl_local(hello.so);3 4 function dl_local( $extensionFile ) 5 /make sure that we are ABLE to load libraries6 if( !(bool)ini_get( enable_dl ) | (bool)ini_get( safe_mode ) ) 7 die( dh_local(): Loading extensions is not permitted.n );8 9 /check to make sure the file exists10 if( !file_exists(dirname(_FILE_) . /. $extensionFile ) ) 11 die( dl_local(): File $extensionFile does not exist.n );12 13 /check the file permissions14 if( !is_executable(dirname(_FILE_) . /. $extensionFile ) ) 15 die( dl_local(): File $extensionFile is not executable.n );16 17 /we figure out the path18 $currentDir = dirname(_FILE_) . /;19 $currentExtPath = ini_get( extension_dir );20 $subDirs = preg_match_all( / , $currentExtPath , $matches );21 unset( $matches );22 /lets make sure we extracted a valid extension path23 if( !(bool)$subDirs ) 24 die( dl_local(): Could not determine a valid extension path extension_dir.n );25 26 $extPathLastChar = strlen( $currentExtPath ) - 1;27 if( $extPathLastChar = strrpos( $currentExtPath , / ) ) 28 $subDirs-;29 30 $backDirStr = ; 31 for( $i = 1; $i = $subDirs; $i+ ) 32 $backDirStr .= .;33 if( $i != $subDirs ) 34 $backDirStr .= /;35 36 37 /construct the final path to load38 $finalExtPath = $backDirStr . $currentDir . $extensionFile;39 /now we execute dl() to actually load the module40 if( !dl( $finalExtPath ) ) 41 die();42 43 /if the module was loaded correctly, we must bow grab the module name44 $loadedExtensions = get_loaded_extensions();45 $thisExtName = $loadedExtensions sizeof( $loadedExtensions ) - 1 ;46 /lastly, we return the extension name47 return $thisExtName;48 /end dl_local()这样的好处是你的php扩展可以随你的php代码走,绿色扩展。随后一个让人关心的问题是,如何添加函数、实现参数传递和返回值添加函数步骤如下:php_hello.h:PHP_FUNCTION(confirm_hello_compiled);/ 括号里面填写函数名hello.czend_function_entry hello_functions = PHP_FE(confirm_hello_compiled, NULL) /* 这里添加一行 */NULL, NULL, NULL /* Must be the last line in hello_functions */;PHP_FUNCTION(confirm_hello_compiled) / 这里写函数体要实现的函数原型其实都一个样,用宏PHP_FUNCTION来包装了一下,另外呢,在hello_functions里面添加了一行信息,表示你这个模块中有这个函数了。那么都是一样的函数原型,如何区分返回值与参数呢?我给一个例子:49 PHP_FUNCTION(hello_strdiff)50 51 char *r1 = NULL, *r2 = NULL;52 int n = 0, m = 0;53 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, ss, &r1, &n, &r2, &m) = FAILURE) 54 return;55 56 while(n & m & *r1 = *r2) 57 r1+;58 r2+;59 n-;60 m-;61 62 if(n = 0) RETURN_LONG(m);63 if(m = 0) RETURN_LONG(n);64 int dn+1m+1;65 int cost;66 int i,j;67 for(i = 0; i = n; i+) di0 = i;68 for(j = 0; j = m; j+) d0j = j;69 for(i = 1; i = n; i+) 70 for(j = 1; j = m; j+) 71 if(r1i-1 = r2j-1) cost = 0;72 else cost = 1;73 int a = MIN(di-1j+1,dij-1+1);74 a = MIN(a, di-1j-1+cost);75 dij = a;76 77 78 RETURN_LONG(dnm);79 这是一个求两个字符串差异度的算法,输入参数两个字符串,返回整型。参数的传递看这里zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, ss, &r1, &n, &r2, &m)把这个当成是scanf来理解好了。类型说明见下表:Booleanbzend_boolLongllongDoubleddoubleStringschar*, intResourcerzval*Arrayazval*Objectozval*zvalzzval*如果想实现可选参数的话,例如一个字符串,一个浮点,再加一个可选的bool型,可以用sd|b来表示。和scanf有一点不同的是,对于字符串,你要提供两个变量来存储,一个是char *,存字符串的地址,一个int,来存字符串的长度。这样有必要的时候,你可以安全的处理二进制数据。那么返回值怎么办呢?使用下面一组宏来表示:RETURN_STRINGRETURN_LONGRETURN_DOUBLERETURN_BOOLRETURN_NULL注意RETURN_STRING有两个参数当你需要复制一份字符串时使用RETURN_STRING(Hello World, 1);否则使用RETURN_STRING(str, 0);这里涉及到了模块中内存的分配,当你申请的内存需要php程序中去释放的话,请参照如下表TraditionalNon-PersistentPersistentmalloc(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数组怎么处理,可以参考/node/view/id/1022翻译:/alexdream/archive/2008/03/24/2213344.aspx更好的文章:/blog/56.html#pp1更详细的呢,可以参考php手册中的Zend API:深入 PHP 内核一章不过这些资料都是英文的。 php扩展基础 本节没有介绍关于脚本引擎基本构造的一些知识,而是直接进入扩展的编码讲解中,因此不要担心你无法立刻获得对扩展整体把握的感觉。假设你正在开发一个网站,需要一个把字符串重复n次的函数。下面是用PHP写的例子: function self_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() : string self_concat(string str, int n) 函数定义文件的一般格式是一个函数一行。你可以定义可选参数和使用大量的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, whether to enable myfunctions support, -enable-myfunctions Include myfunctions support) 现在剩下的事情就是在PHP原代码树根目录下运行./buildconf,该命令会生成一个新的配置脚本。通过查看./configure -help输出信息,可以检查新的配置选项是否被包含到配置文件中。现在,打开你喜好的配置选项开关和-enable-myfunctions重新配置一下PHP。最后的但不是最次要的是,用make来重新编译PHP。 ext_skel应该把两个PHP函数添加到你的扩展骨架了:打算实现的self_concat()函数和用于检测myfunctions 是否编译到PHP的confirm_myfunctions_compiled()函数。完成PHP的扩展开发后,可以把后者去掉。 运行这个脚本会出现类似下面的输出:Congratulations! You have successfully modified ext/myfunctionsconfig.m4. Module myfunctions is now compiled into PHP. 另外,ext_skel脚本生成一个叫myfunctions.php的脚本,你也可以利用它来验证扩展是否被成功地编译到PHP。它会列出该扩展所支持的所有函数。 现在你学会如何编译扩展了,该是真正地研究self_concat()函数的时候了。 下面就是ext_skel脚本生成的骨架结构: /* proto string self_concat(string str, int n)*/PHP_FUNCTION(self_concat)char *str = NULL;int argc = ZEND_NUM_ARGS();int str_len;long n;if (zend_parse_parameters(argc TSRMLS_CC, sl, &str, &str_len, &n) = FAILURE)return;php_error(E_WARNING, self_concat: not yet implemented);/* */zend_parse_parameters 详解自动生成的PHP函数周围包含了一些注释,这些注释用于自动生成代码文档和vi、Emacs等编辑器的代码折叠。函数自身的定义使用了宏PHP_FUNCTION(),该宏可以生成一个适合于Zend引擎的函数原型。逻辑本身分成语义各部分,取得调用函数的参数和逻辑本身。 为了获得函数传递的参数,可以使用zend_parse_parameters()API函数。下面是该函数的原型:zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ); 第一个参数是传递给函数的参数个数。通常的做法是传给它ZEND_NUM_ARGS()。(ZEND_NUM_ARGS() 来表示对传入的参数“有多少要多少”)这是一个表示传递给函数参数总个数的宏。第二个参数是为了线程安全,总是传递TSRMLS_CC宏,后面会讲到。第三个参数是一个字符串,指定了函数期望的参数类型,后面紧跟着需要随参数值更新的变量列表。因为PHP采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。例如,如果用户传递一个整数变量,可函数需要一个浮点数,那么zend_parse_parameters()就会自动地把整数转换为相应的浮点数。如果实际值无法转换成期望类型(比如整形到数组形),会触发一个警告。下表列出了可能指定的类型。我们从完整性考虑也列出了一些没有讨论到的类型。 类型指定符对应的C类型描述llong符号整数ddouble浮点数schar *, int二进制字符串,长度bzend_bool逻辑型(1或0)rzval *资源(文件指针,数据库连接等)azval *联合数组ozval *任何类型的对象Ozval *指定类型的对象。需要提供目标对象的类类型zzval *无任何操作的zval 为了容易地理解最后几个选项的含义,你需要知道zval是Zend引擎的值容器1。无论这个变量是布尔型,字符串型或者其他任何类型,其信息总会包含在一个zval联合体中。本章中我们不直接存取zval,而是通过一些附加的宏来操作。下面的是或多或少在C中的zval, 以便我们能更好地理解接下来的代码。 typedef union _zval long lval;double dval;struct char *val;int len; str;HashTable *ht;zend_object_value obj; zval; 在我们的例子中,我们用基本类型调用zend_parse_parameters(),以本地C类型的方式取得函数参数的值,而不是用zval容器。为了让zend_parse_parameters()能够改变传递给它的参数的值,并返回这个改变值,需要传递一个引用。仔细查看一下self_concat(): if (zend_parse_parameters(argc TSRMLS_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(),除非你不介意函数在二进制字符串下不能工作。二进制字符串是包含有nulls的字符串。二进制格式包括图象文件,压缩文件,可执行文件和更多的其他文件。”l” 只需要一个参数,所以我们传递给它n的引用。尽管为了清晰起见,骨架脚本生成的C变量名与在函数原型定义文件中的参数名一样;这样做不是必须的,尽管在实践中鼓励这样做。回到转换规则中来。下面三个对self_concat()函数的调用使str, str_len和n得到同样的值: self_concat(321, 5);self_concat(321, 5);self_concat(321, 5);str points to the string 321, str_len equals 3, and n equals 5.str 指向字符串321,str_len等于3,n等于5。 在我们编写代码来实现连接字符串返回给PHP的函数前,还得谈谈两个重要的话题:内存管理、从PHP内部返回函数值所使用的API! 内存管理 用于从堆中分配内存的PHP API几乎和标准C API一样。在编写扩展的时候,使用下面与C对应(因此不必再解释)的API函数: emalloc(size_t size);efree(void *ptr);ecalloc(size_t nmemb, size_t size);erealloc(void *ptr, size_t size);estrdup(const char *s);estrndup(const char *s, unsigned int length); 在这一点上,任何一位有经验的C程序员应该象这样思考一下:“什么?标准C没有strndup()?”是的,这是正确的,因为GNU扩展通常在Linux下可用。estrndup()只是PHP下的一个特殊函数。它

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论