php调用C代码的方法详解和zend_parse_parameters函数详解
在php程序中需要用到C代码,应该是下面两种情况:
- if(!extension_loaded("hello")) {
- dl_local("hello.so");
- }
- function dl_local( $extensionFile ) {
- //make sure that we are ABLE to load libraries
- if( !(bool)ini_get( "enable_dl" ) || (bool)ini_get( "safe_mode" ) ) {
- die( "dh_local(): Loading extensions is not permitted./n" );
- }
- //check to make sure the file exists
- if( !file_exists(dirname(__FILE__) . "/". $extensionFile ) ) {
- die( "dl_local(): File '$extensionFile' does not exist./n" );
- }
- //check the file permissions
- if( !is_executable(dirname(__FILE__) . "/". $extensionFile ) ) {
- die( "dl_local(): File '$extensionFile' is not executable./n" );
- }
- //we figure out the path
- $currentDir = dirname(__FILE__) . "/";
- $currentExtPath = ini_get( "extension_dir" );
- $subDirs = preg_match_all( "////" , $currentExtPath , $matches );
- unset( $matches );
- //lets make sure we extracted a valid extension path
- if( !(bool)$subDirs ) {
- die( "dl_local(): Could not determine a valid extension path [extension_dir]./n" );
- }
- $extPathLastChar = strlen( $currentExtPath ) - 1;
- if( $extPathLastChar == strrpos( $currentExtPath , "/" ) ) {
- $subDirs--;
- }
- $backDirStr = "";
- for( $i = 1; $i <= $subDirs; $i++ ) {
- $backDirStr .= "..";
- if( $i != $subDirs ) {
- $backDirStr .= "/";
- }
- }
- //construct the final path to load
- $finalExtPath = $backDirStr . $currentDir . $extensionFile;
- //now we execute dl() to actually load the module
- if( !dl( $finalExtPath ) ) {
- die();
- }
- //if the module was loaded correctly, we must bow grab the module name
- $loadedExtensions = get_loaded_extensions();
- $thisExtName = $loadedExtensions[ sizeof( $loadedExtensions ) - 1 ];
- //lastly, we return the extension name
- return $thisExtName;
- }//end dl_local()
这样的好处是你的php扩展可以随你的php代码走,绿色扩展。
- PHP_FUNCTION(hello_strdiff)
- {
- char *r1 = NULL, *r2 = NULL;
- int n = 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);
- int d[n+1][m+1];
- int cost;
- int i,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;
- else cost = 1;
- int a = 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]);
- }
这是一个求两个字符串差异度的算法,输入参数两个字符串,返回整型。
Boolean | b |
zend_bool |
Long | l |
long |
Double | d |
double |
String | s |
char*, int |
Resource | r |
zval* |
Array | a |
zval* |
Object | o |
zval* |
zval | z |
zval* |
如果想实现可选参数的话,例如一个字符串,一个浮点,再加一个可选的bool型,可以用"sd|b"来表示。
Traditional | Non-Persistent | Persistent |
---|---|---|
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中列出的这些好了。
更好的文章:http://www.toplee.com/blog/56.html#pp1
本节没有介绍关于脚本引擎基本构造的一些知识,而是直接进入扩展的编码讲解中,因此不要担心你无法立刻获得对扩展整体把握的感觉。假设你正在开发一个网站,需要一个把字符串重复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的扩展开发后,可以把后者去掉。
<?php
print confirm_myfunctions_compiled("myextension");
?>
运行这个脚本会出现类似下面的输出:
"Congratulations! You have successfully modified ext/myfunctions
config.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");
}
/* }}} */
自动生成的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类型 |
描述 |
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, 以便我们能更好地理解接下来的代码。
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下的一个特殊函数。它的行为与estrdup()相似,但是可以指定字符串重复的次数(不需要结束空字符),同时是二进制安全的。这是推荐使用estrndup()而不是estrdup()的原因。
在几乎所有的情况下,你应该使用这些内存分配函数。有一些情况,即扩展需要分配在请求中永久存在的内存,从而不得不使用malloc(),但是除非你知道你在做什么,你应该始终使用以上的函数。如果没有使用这些内存函数,而相反使用标准C函数分配的内存返回给脚本引擎,那么PHP会崩溃。
这些函数的优点是:任何分配的内存在偶然情况下如果没有被释放,则会在页面请求的最后被释放。因此,真正的内存泄漏不会产生。然而,不要依赖这一机制,从调试和性能两个原因来考虑,应当确保释放应该释放的内存。剩下的优点是在多线程环境下性能的提高,调试模式下检测内存错误等。
还有一个重要的原因,你不需要检查这些内存分配函数的返回值是否为null。当内存分配失败,它们会发出E_ERROR错误,从而决不会返回到扩展。
从PHP函数中返回值
扩展API包含丰富的用于从函数中返回值的宏。这些宏有两种主要风格:第一种是RETVAL_type()形式,它设置了返回值但C代码继续执行。这通常使用在把控制交给脚本引擎前还希望做的一些清理工作的时候使用,然后再使用C的返回声明 ”return” 返回到PHP;后一个宏更加普遍,其形式是RETURN_type(),他设置了返回类型,同时返回控制到PHP。下表解释了大多数存在的宏。
设置返回值并且结束函数 |
设置返回值 |
宏返回类型和参数 |
RETURN_LONG(l) |
RETVAL_LONG(l) |
整数 |
RETURN_BOOL(b) |
RETVAL_BOOL(b) |
布尔数(1或0) |
RETURN_NULL() |
RETVAL_NULL() |
NULL |
RETURN_DOUBLE(d) |
RETVAL_DOUBLE(d) |
浮点数 |
RETURN_STRING(s, dup) |
RETVAL_STRING(s, dup) |
字符串。如果dup为1,引擎会调用estrdup()重复s,使用拷贝。如果dup为0,就使用s |
RETURN_STRINGL(s, l, dup) |
RETVAL_STRINGL(s, l, dup) |
长度为l的字符串值。与上一个宏一样,但因为s的长度被指定,所以速度更快。 |
RETURN_TRUE |
RETVAL_TRUE |
返回布尔值true。注意到这个宏没有括号。 |
RETURN_FALSE |
RETVAL_FALSE |
返回布尔值false。注意到这个宏没有括号。 |
RETURN_RESOURCE(r) |
RETVAL_RESOURCE(r) |
资源句柄。 |
php调用C代码的方法详解和zend_parse_parameters函数详解的更多相关文章
- 【ES6 】ES6 解构赋值--函数参数解构赋值
函数的参数也可以使用解构赋值. function add([x, y]){ return x + y; } add([1, 2]); 上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻 ...
- 如何实现 javascript “同步”调用 app 代码
在 App 混合开发中,app 层向 js 层提供接口有两种方式,一种是同步接口,一种一异步接口(不清楚什么是同步的请看这里的讨论).为了保证 web 流畅,大部分时候,我们应该使用异步接口,但是某些 ...
- C++调用C代码的两种方式
由于C++支持函数重载,在编译函数代码的时候会加上参数类型的信息,而C编译只有函数名信息,导致C++直接调用C代码在链接的时候会出现函数未定义的问题.解决这种问题有两种方法.方法一:在写C代码的时候考 ...
- 调用未绑定的父类方法和使用supper 函数 之间的选择.
class New_int(int): # 定义一个新的类 继承 int 类 def __add__(self,other): # 重写 + 运算符 # __add__ 就是 int 中 + 的行为 ...
- 临时调用call()与apply()方法
当在某个局域范围内要调用构造函数中或者其他局域范围内的方法 此时可以用到临时调用方法call与apply 虽然这两个方法都是起临时调用的功能,但是用法不一样 call(obj,val) obj:对象名 ...
- vc中调用Com组件的方法详解
vc中调用Com组件的方法详解 转载自:网络,来源未知,如有知晓者请告知我.需求:1.创建myCom.dll,该COM只有一个组件,两个接口: IGetRes--方法Hello(), IGet ...
- MSScriptControl详解(可实现在C#等语言中调用JAVASCRIPT代码)
ScriptControl接口 属性名称 类型 备注 AllowUI BOOL 检测是否允许运行用户的接口元素.如果为False,则诸如消息框之类的界面元素不可见. CodeObject Object ...
- 菜刀(代码执行)函数和命令执行函数详解及Getshell方法
i春秋作家:大家奥斯的哦 原文来自:https://bbs.ichunqiu.com/thread-41471-1-1.html 代码执行函数 VS 命令执行函数 一直想整理这两块的内容,但是一直没时 ...
- 【随笔】菜刀(代码执行)函数和命令执行函数详解及Getshell方法
代码执行函数 VS 命令执行函数 一直想整理这两块的内容,但是一直没时间弄,直到前两天碰上一个写入了菜刀马但是死活连不上菜刀的站,顿时不知道怎么继续了,所以就趁这个机会整理了一下代码执行函数怎么get ...
随机推荐
- [Python爬虫] 之九:Selenium +phantomjs抓取活动行中会议活动(单线程抓取)
思路是这样的,给一系列关键字:互联网电视:智能电视:数字:影音:家庭娱乐:节目:视听:版权:数据等.在活动行网站搜索页(http://www.huodongxing.com/search?city=% ...
- Linux下解压tar.xz文件
xz -d glib-2.14.tar.xz tar -xvf glib-2.14.tar 前面一个是将xz文件解压成tar文件,后面一个是将tar文件解压. xz使用格式:压缩: xz -z fil ...
- atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較
atitit.提升开发效率---使用server控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比較 例如以下列举了server控件生命周期所要经历的11个阶段. (1)初始 ...
- 修改ASPCMS升级扩展功能
修改 inc/aspcms_templateFun.asp 查找"content=decodeHtml(rsObj("Content"))" 替换为”conte ...
- 云计算之路-阿里云上:SLB故障引发的网站不能正常访问
2013年8月22日23:50~23:58左右,由于阿里云SLB(负载均衡)故障造成网站不能正常访问,给大家带来了麻烦,望大家谅解! 8月19日我们收到阿里云的短信通知: 尊敬的阿里云用户: ...
- Maven版本的ssm框架项目常见依赖pom.xml
<properties> <junit.version>4.12</junit.version> <spring.version>4.3.1.RELEA ...
- 一个简单的int型C++单链表的实现
IntSLList.h //************************ intSLList.h ************************** // singly-linked list ...
- ARM 指令集版本和ARM 版本z
a9是cortex-a9的简称,属于v7指令集,属于目前比较新的了.arm9就是arm9,属于v5指令集,arm9后面的是arm11,属于v6指令集,之前的是arm7,属于v4指令集.虽然他们之间差别 ...
- 无埋点数据收集和adb monkey测试屏蔽通知栏
简单记录百度移动统计android无埋点sdk使用和monkey测试屏蔽通知栏的问题 1.无埋点sdk使用 很简单,下载完sdk后导入到项目中 , 参考sdk文档进行就可以了,个人觉得比友盟还简单,几 ...
- javascript 自定义Map
迁移时间:2017年5月25日08:24:19 Author:Marydon 三.自定义Map数据格式 需特别注意的是: js中没有像java中的Map数据格式,js自带的map()方法用于:返回 ...