当前位置:Gxlcms > PHP教程 > 13结尾

13结尾

时间:2021-07-01 10:21:17 帮助过:7人阅读

PHP扩展开发-v1.0

PHP扩展开发

Author: 牛家明

Iamnew <[email protected]>

QQ: 947847775

1 前言

这篇文章是我个人的学习笔记,我把这篇文章送给所有喜欢PHP语言,喜欢PHP扩展开发的同行、同学们。

2 前期准备

闲话不和大家撤了,如果您想学习php的扩展开发,相信您对php基础知识已经有了一定的了解。PHP扩展时用C语言编写的,如果您还不知道C语言

里面指针式什么东西,建议您先移步他出,好好学习一下C语言,大家都在骂谭浩强的《C语言程序设计》,但是我仍然建议您好好看看这本书。

做PHP开发得先准备一下环境吧。说起来容易,对于一些新手来说恐怕也是一场噩梦。建议您安装UBUNTU LINUX,网上有很多的linux安装教程,相信我不用多说了。

我们假设您已经安装了linux,我用的debain,不过ubuntu的同学不用担心,因为ubuntu是debain变话而来的,所谓万变不离其宗,相信您使用ubuntu可以达到同样的效果。

2.1 开发环境搭建

首先,我们安装一下php的开发环境:PHP+APACHE2+MYSQL

UBUNTU下的安装很简单,简单到您只需要执行下面的命令就可以了:

sudo apt-get install apache2

sudo apt-get install php5

sudo apt-get install libapache2-mod-php5

sudo apt-get install mysql-server

sudo apt-get install libapache2-mod-auth-mysql

sudo apt-get install php5-mysql

sudo /etc/init.d/apache2 restart

2.2 源码部署

进行php扩展开发我们还需要下载一份php的源代码,我们以PHP5.3为例进行php扩张开发讲解,下面是一些下载地址,为了防止下载链接失效,我会不断更新链接,当然PHP版本不会变化^_^

http://cn2.php.com/get/php-5.3.19.tar.gz/from/this/mirror

你也可以到www.php.com上面,点击download下载源码。

假设您已经或得到了源码,下面我们将源代码解压缩 tar xzvf php-5.3.19 如果这个命令不能正确解压缩的话请使用tar xvf php-5.3.19试试,再不行的话用rar解压缩吧,我相信你总有办法将这个文件解压缩~ 多大的事 - _ -

我们的php扩展是在linux上面开发的,如果您想在win上面开发,这片文章恐怕不太适合您了。但是技术这种东西都是触类旁通,希望您在win上面部署好了环境再来看看我的文章。

3 一切都是从HelloWorld开始的

还记得第一次写代码,是用C语言打印出来了HelloWorld,那天我兴奋激动了一天。希望你你看完本页内容后自己也写一个helloworld的php扩展。

第一个扩展程序,改怎么写呢?

其实很简单~~~ 开始吧~~

上次我们说,我们第一步需要卸载php的源代码,你下载好了吗?

建议:建议大家在开发时,只安装一个php,其实在一个系统中你可以安装多个php,但是为了避免前期很多不必要的解释,读者最好安装一个。等你对php娴熟后,多个php就不在话下了。

3.1 进入扩展目录 cd php-5.3.19/ext

ext文件夹存放了是php扩展的源代码,同样的,我们编写扩展也需要这里面进行

大家可以看看ext文件夹下各个文件夹的名字:mysql、xml、zip、sqlite...这些都是php的扩张

3.2 建立扩展开发框架 ./ext_skel --extname=helloworld

执行上面的命令建立扩张开发的框架,这是你会发现ext文件夹下面多了一个叫helloworld的文件夹,同时命令行也输出了一些文本,

我们先把这些东西放在这里,不做解释,稍后大家在看看输出的命令是什么意思。

记者这幅图奥,我们下面会分析

3.3 进入php源码的根目录, 编辑文件 vi ext/helloworld/config.m4

去掉这个文件中的几行注释

所谓的取消注释,就是讲16~18行前面的dnl去掉而已(不同版本的php源码行数可能不一样,请大家注意)

注释取消后,保存文件并且退出

3.4 在php根目录执行命令./buildconf --force 这一步以后的文章中进行解释

3.5 在php源码的根目录编译php程序,注意命令为 ./configure--with-helloworld

3.6 进入我们的扩张目录helloworld,执行命令 phpize

3.7 在helloworld目录编译我们的扩展./configure --with-php-config=/usr/local/bin/php-config(使用你自己环境的php-config)--enable-helloworld

3.8 执行make命令 make

我在执行make的时候,系统报错,错误信息为

/home/work/src/php-5.3.19/ext/helloworld/helloworld.c:43: error:'PHP_FE_END' undeclared here (not in a function)

make: *** [helloworld.lo] Error 1

此时,请大家讲helloworld.c中的第43行中PHP_FE_END换成{NULL,NULL, NULL}

替换完成后,请大家执行make命令重新编译

3.9 安装我们的扩展

有些同学可能知道,php的程序可以使用makeinstall进行安装,但是为了更好的理解php的工作原理,

我们采用手动安装php扩展的方式进行安装,其实php的安装非常简单

3.9.1 找到php的扩展安装目录

[email protected]:~$ php -r "phpinfo();" | grep extension_dir

extension_dir => /usr/lib/php5/20090626+lfs =>/usr/lib/php5/20090626+lfs

我们需要将我们的扩张安装到/usr/lib/php5/20090626+lfs目录下面,

在helloworld目录下面执行命令

sudo cpmodules/helloworld.so /usr/lib/php5/20090626+lfs 目录/usr/lib/php5/20090626+lfs需要根据自己的实际情况作出改动

3.9.2 在php.ini中打开我们的扩展

同样,请执行下面的命令

[email protected]:~$ php -r "phpinfo();" | grep"php.ini"

Configuration File (php.ini) Path => /etc/php5/cli

Loaded Configuration File => /etc/php5/cli/php.ini

可以看出php.ini的路径为/etc/php5/cli/php.ini

sudo vi /etc/php5/cli/php.ini

在文件的最后一行添加代码 extension=helloworld.so

3.10 验证我们的扩展时否安装成功

你可以通过执行下面的命令进行验证 php -r "phpinfo();" | grep helloworld

如果你安装成功的话,应该看到下面的信息

[email protected]:/usr/lib/php5/20090626+lfs# php -r "phpinfo();" |grep helloworld

helloworld

helloworld support => enabled

至此,php扩展安装完成

也许你会问,我该怎么打印helloworld呢,别急,下面接着就将helloworld程序了。

3.11 进入扩展helloworld目录,编辑文件php_helloworld.h,在最后一行添加函数 PHP_FUNCTION(fun_helloworld);

3.12 在helloworld.c中实现我们的函数

fun_helloworld函数加入到helloworld_functions[]

3.13 编译扩展 make

3.14 按照step 9-2 重新安装我们的扩展

3.15 验证扩展函数

php -r "echo fun_helloworld();"

如果一切顺利的话,你就可以看到我们的问候语句了Hello World !

3.16 写在后面的话

也许你按照我的步骤走到最后,你看到了我们的问候语hello world,也许你没有,但是我已经尽力的把开发的过程描述的尽量详细,请大家勿要骂我。其实做程序开发的过程就是一个不断解决问题的过程,如果你在我上面的步骤中遇到了问题,那么我要恭喜你,因为你得到一个提高自己能力的机会。在解决问题的过程中,百度、谷歌是我们最好的帮手,虽然你也可以通过其他教程或者官方文档解决问题,但是百度、谷歌对于新手来说却是最快解决问题的方式。

说了这么多,无非是想告诉大家:不要害怕在学习的过程中遇到问题,因为这些问题才是我们进步的基石。

4 SAPI简介

要想写好php扩展,了解一下SAPI肯定是少不了的。也许你看完本节的内容你还不能很好的掌握SAPI,不过没有关系,这并不会影响到你后续的php扩展开发,但是我建议读者还是细细地看完这一节,如果有问题的话可以给我联系。

SAPI(ServerApplication Programming Interface)指是PHP具体应用编程接口。PHP脚本的执行方式有很多,常见的就是把PHP放在web服务器上面执行、在命令行直接通过php test.php的方式执行,其实php脚本也可以嵌入在其他程序中执行。

目前常见的sapi实现方式有apache模块,fpm,cgi,cli等,而这些实现可以分为三类:单进程SAPI、多进程SAPI、多线程SAPI。

4.1 单进程SAPI

CLI/CGI模式的PHP属于单进程SAPI实现方式(或者叫做实现模型)。这类的请求在处理一次后就关闭。它的生命周期如下:

请记住:每次请求都会执行上面所有的过程。

给大家解释一下:假如你使用命令行方式运行php文件或者通过web服务器以cgi方式访问一个php文件,每一次请求你都会经历下面的过程:

1. 执行每一个扩展的MINIT方法

2. 执行每个扩展的RINIT方法

3. 执行你要访问的php文件

4. 执行每个扩展的RSHUTDOWN方法

5. 执行清理工作(针对你要执行的php文件而言)

6. 执行每个扩展的MSHUTDOWN方法

7. php执行结束

看到MINIT, RINIT,RSHUTDWON, MSSHUTDOWN了吗,是不是有点面熟啊?

4.2 多进程SAPI

如果PHP编译成为apache的一个模块处理PHP请求,那么apache一般会采用多进程的模式,apache启动后会创建多个子进程,每个子进程在整个生命周期中,可能会处理多次请求。只有在apache关闭,这些子进程才会关闭。这种模式的SAPI生命周期如下:

解释:上图顶部红色部分在apache fork出来这个子进程后就会自行,而中间蓝颜色表示部分每个请求时执行一次,一次请求只能在被一个子进程处理,上图底部红色部分是子进程结束的时候执行的。对于每个进程而言,MINIT函数只会执行一次,但是每次请求都会执行RINIT RSHUTDOWN函数,进程结束时执行MSHUTDOWN函数。

4.3 多线程SAPI

多线程模式和多进程中的某个进程类似,不同的是整个进程的声明周期内会并行重复请求开始->请求结束,它的生命周期如下图:

我们依次讲解一下apache的对请求的处理流程、php文件的处理流程、php扩展执行的流程。如果您想深入了解扩展,并且向在扩展开发方面有所成就的话,那么我建议您详细的看一下这一节,也许你看不懂,但是没有关系,以后我们还会详细讲解。

5 php扩展调用php.ini配置

本节中我们读取一下php.ini文件中的配置。其实,读取php.ini配置的方式挺多,本次我们只讲一种,如果有兴趣的话,大家可以各自研究一下。

废话少说,我们更改一下之前的讲解方式,本次我们直接上代码。(其实是因为php扩展读取配置文件太简单了)

(假设我们建立了一个ini_read的扩展,代码的改动部分我们用黄颜色标出

5.1 更改配置

在php.ini中增加配置ini_read.helloworld=hellohellohello

5.2 头文件改动

php_ini_read.h的改动如下:(改动的地方我用黄颜色

#ifndef PHP_INI_READ_H

#define PHP_INI_READ_H

externzend_module_entry ini_read_module_entry;

#define phpext_ini_read_ptr &ini_read_module_entry

#ifdef PHP_WIN32

# define PHP_INI_READ_API __declspec(dllexport)

#elif defined(__GNUC__) &&__GNUC__ >= 4

# define PHP_INI_READ_API __attribute__((visibility("default")))

#else

# define PHP_INI_READ_API

#endif

#ifdef ZTS

#include "TSRM.h"

#endif

PHP_MINIT_FUNCTION(ini_read);

PHP_MSHUTDOWN_FUNCTION(ini_read);

PHP_RINIT_FUNCTION(ini_read);

PHP_RSHUTDOWN_FUNCTION(ini_read);

PHP_MINFO_FUNCTION(ini_read);

/*

Declare any global variables you may need between the BEGIN

and END macros here:

ZEND_BEGIN_MODULE_GLOBALS(ini_read)

long global_value;

char *global_string;

ZEND_END_MODULE_GLOBALS(ini_read)

*/

/* In every utility function you addthat needs to use variables

in php_ini_read_globals, call TSRMLS_FETCH(); after declaring other

variables used by that function, or better yet, pass in TSRMLS_CC

after the last function argument and declare your utility function

with TSRMLS_DC after the last declared argument. Always refer to

the globals in your function as INI_READ_G(variable). You are

encouraged to rename these macros something shorter, see

examples in any other php module directory.

*/

#ifdef ZTS

#define INI_READ_G(v)TSRMG(ini_read_globals_id, zend_ini_read_globals *, v)

#else

#define INI_READ_G(v)(ini_read_globals.v)

#endif

#endif /* PHP_INI_READ_H */

/*

* Local variables:

* tab-width: 4

* c-basic-offset: 4

* End:

* vim600: noet sw=4 ts=4 fdm=marker

* vim<600: noet sw=4 ts=4

*/

PHP_FUNCTION(helloworld);

5.3 源文件改动

ini_read.c的改动如下

#ifdef HAVE_CONFIG_H

#include "config.h"

#endif

#include "php.h"

#include "php_ini.h"

#include"ext/standard/info.h"

#include "php_ini_read.h"

/* If you declare any globals inphp_ini_read.h uncomment this:

ZEND_DECLARE_MODULE_GLOBALS(ini_read)

*/

/* True global resources - no need forthread safety here */

static int le_ini_read;

/* {{{ ini_read_functions[]

*

* Every user visible function must have anentry in ini_read_functions[].

*/

constzend_function_entry ini_read_functions[]={

PHP_FE(helloworld, NULL) /* Fortesting, remove later. */

{NULL, NULL, NULL}

};

/* }}} */

/* {{{ ini_read_module_entry

*/

zend_module_entry ini_read_module_entry={

#if ZEND_MODULE_API_NO >= 20010901

STANDARD_MODULE_HEADER,

#endif

"ini_read",

ini_read_functions,

PHP_MINIT(ini_read),

PHP_MSHUTDOWN(ini_read),

NULL,

NULL,

NULL,

#if ZEND_MODULE_API_NO >= 20010901

"0.1",/* Replace with version number for yourextension */

#endif

STANDARD_MODULE_PROPERTIES

};

/* }}} */

#ifdef COMPILE_DL_INI_READ

ZEND_GET_MODULE(ini_read)

#endif

/* {{{ PHP_INI

*/

PHP_INI_BEGIN()

PHP_INI_ENTRY("ini_read.helloworld", "foobar",PHP_INI_ALL, NULL)

PHP_INI_END()

/* }}} */

/* {{{ php_ini_read_init_globals

*/

/* Uncomment this function if you haveINI entries

static voidphp_ini_read_init_globals(zend_ini_read_globals *ini_read_globals)

{

ini_read_globals->global_value = 0;

ini_read_globals->global_string = NULL;

}

*/

/* }}} */

/* {{{ PHP_MINIT_FUNCTION

*/

PHP_MINIT_FUNCTION(ini_read)

{

REGISTER_INI_ENTRIES();

return SUCCESS;

}

/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION

*/

PHP_MSHUTDOWN_FUNCTION(ini_read)

{

UNREGISTER_INI_ENTRIES();

return SUCCESS;

}

/* }}} */

/* Remove if there's nothing to do atrequest start */

/* {{{ PHP_RINIT_FUNCTION

*/

PHP_RINIT_FUNCTION(ini_read)

{

return SUCCESS;

}

/* }}} */

/* Remove if there's nothing to do atrequest end */

/* {{{ PHP_RSHUTDOWN_FUNCTION

*/

PHP_RSHUTDOWN_FUNCTION(ini_read)

{

return SUCCESS;

}

/* }}} */

/* {{{ PHP_MINFO_FUNCTION

*/

PHP_MINFO_FUNCTION(ini_read)

{

php_info_print_table_start();

php_info_print_table_header(2,"ini_read support","enabled");

php_info_print_table_end();

/* Remove comments if you have entries in php.ini

DISPLAY_INI_ENTRIES();

*/

}

/* }}} */

/* }}} */

/* The previous line is meant for vimand emacs, so it can correctly fold and

unfold functions in source code. See the corresponding marks just before

function definition, where the functions purpose is also documented.Please

follow this convention for the convenience of others editing your code.

*/

/*

* Local variables:

* tab-width: 4

* c-basic-offset: 4

* End:

* vim600: noet sw=4 ts=4 fdm=marker

* vim<600: noet sw=4 ts=4

*/

PHP_FUNCTION(helloworld)

{

RETURN_STRING(INI_STR("ini_read.helloworld"),1);

}

5.4 结果验证

上面步骤执行完了后,根据之前安装扩张的方式安装一下我们的扩展吧。

如果你安装完了扩展,那么运行下面的命令您就可以看到输出了

php -r "echohelloworld();" 此时的输出应该为hellohellohello

6 全局变量

6.1 抛砖引玉

将这一节的目的是想交给大家,如和声明一个变量。使得这个变量针对每次请求独立,也就是说,同一次请求我们访问的变量是同一个,不同的请求我们使用的变量不是同一个。

说道这里我先抛出一个问题:既然要实现上面的要求,那么我们该怎么办呢?我应该在哪里声明我的全局变量呢?

还记得SAPI简介那一张吗?SAPI的实现有三种方式,单进程,多进程,多线程,但是对于每一次而言,都必须执行的几个过程为RINIT RSHUTDOWN….说道这里你意识到了吗。我们是不是在RINIT过程的时候初始化我们的全局常量,这样每次请求的时候这个变量的值都会变成默认值。还没有看明白?看看下面的流程你也许就懂了。

1. 首先在.h文件中声明全局变量

2. 在RINIT过程时初始化这个变量

3. 调用变量

6.2 实现方式

我们还是直接上源代码:我把一些无用的注释去掉了,希望大家别介意

头文件php-iamnew.h

#ifndef PHP_IAMNEW_H

#define PHP_IAMNEW_H

extern zend_module_entry iamnew_module_entry;

#define phpext_iamnew_ptr &iamnew_module_entry

#ifdef PHP_WIN32

# definePHP_IAMNEW_API __declspec(dllexport)

#elif defined(__GNUC__) && __GNUC__ >= 4

# definePHP_IAMNEW_API __attribute__ ((visibility("default")))

#else

# definePHP_IAMNEW_API

#endif

#ifdef ZTS

#include "TSRM.h"

#endif

PHP_MINIT_FUNCTION(iamnew);

PHP_MSHUTDOWN_FUNCTION(iamnew);

PHP_RINIT_FUNCTION(iamnew);

PHP_RSHUTDOWN_FUNCTION(iamnew);

PHP_MINFO_FUNCTION(iamnew);

PHP_FUNCTION(confirm_iamnew_compiled); /* For testing, remove later. */

ZEND_BEGIN_MODULE_GLOBALS(iamnew)

long counter;

ZEND_END_MODULE_GLOBALS(iamnew)

#ifdef ZTS

#define IAMNEW_G(v) TSRMG(iamnew_globals_id,zend_iamnew_globals *, v)

#else

#define IAMNEW_G(v) (iamnew_globals.v)

#endif

#endif

PHP_FUNCTION(test_global_value);

源文件iamnew.c

#ifdef HAVE_CONFIG_H

#include "config.h"

#endif

#include "php.h"

#include "php_ini.h"

#include "ext/standard/info.h"

#include "php_iamnew.h"

ZEND_DECLARE_MODULE_GLOBALS(iamnew) //声明全局变量

static int le_iamnew;

const zend_function_entry iamnew_functions[]={

PHP_FE(test_global_value,NULL)

{NULL,NULL,NULL} //此处修改以后不再解释

};

zend_module_entry iamnew_module_entry ={

#if ZEND_MODULE_API_NO >= 20010901

STANDARD_MODULE_HEADER,

#endif

"iamnew",

iamnew_functions,

PHP_MINIT(iamnew),

PHP_MSHUTDOWN(iamnew),

PHP_RINIT(iamnew),

PHP_RSHUTDOWN(iamnew),

PHP_MINFO(iamnew),

#if ZEND_MODULE_API_NO >= 20010901

"0.1",

#endif

STANDARD_MODULE_PROPERTIES

};

#ifdef COMPILE_DL_IAMNEW

ZEND_GET_MODULE(iamnew)

#endif

// 这个函数之前是被注释的,去掉注释,并且函数内容为空即可

static void php_iamnew_init_globals(zend_iamnew_globals*iamnew_globals)

{

}

PHP_MINIT_FUNCTION(iamnew)

{

ZEND_INIT_MODULE_GLOBALS(iamnew, php_iamnew_init_globals,NULL);

return SUCCESS;

}

PHP_MSHUTDOWN_FUNCTION(iamnew)

{

return SUCCESS;

}

PHP_RINIT_FUNCTION(iamnew)

{

IAMNEW_G(counter)=0; //初始化

return SUCCESS;

}

PHP_RSHUTDOWN_FUNCTION(iamnew)

{

return SUCCESS;

}

PHP_MINFO_FUNCTION(iamnew)

{

php_info_print_table_start();

php_info_print_table_header(2,"iamnew support","enabled");

php_info_print_table_end();

}

// 增加测试函数

PHP_FUNCTION(test_global_value)

{

IAMNEW_G(counter)++;

RETURN_LONG(IAMNEW_G(counter));

}

6.3 结果验证

修改完成后,编译安装我们的扩展,执行下面的命令进行测试

php -r "echotest_global_value(); test_global_value();"

以后我们可能不在对这些简单的结果进行验证了,但是为了学习效果,建议大家自己验证一下。

6.4 实现原理

6.4.1 知识点1

我们先看一下头文件中声明全局变量的两个宏

ZEND_BEGIN_MODULE_GLOBALS

ZEND_END_MODULE_GLOBALS

我们看一下这两个宏的展开内容:

#define ZEND_BEGIN_MODULE_GLOBALS(module_name) \

typedef struct _zend_##module_name##_globals {

#define ZEND_END_MODULE_GLOBALS(module_name) \

}zend_##module_name##_globals;

从展开信息中我们可以看到,这两个宏仅仅是定了一个叫做zend_##module##_globals的结构体,而ZEND_BEGIN_MODULE_GLOBALS和ZEND_END_MODULE_GLOBALS就是结构体zend_##module##_globals的成员变量。

6.4.2 知识点2

我们再来看一下ZEND_DECLARE_MODULE_GLOBALS这句话是

#define ZEND_DECLARE_MODULE_GLOBALS(module_name) \

ts_rsrc_idmodule_name##_globals_id;

其实ts_rsrc_id就是int类型,查看ts_rsrc_id的定义就可以知道:typedef int ts_rsrc_id;

6.4.3 知识点3

在一个宏声明ZEND_INIT_MODULE_GLOBALS,查看一下这个宏的定义

#define ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor,globals_dtor) \

ts_allocate_id(&module_name##_globals_id,sizeof(zend_##module_name##_globals), (ts_allocate_ctor) globals_ctor,(ts_allocate_dtor) globals_dtor);

从定义来看,ZEND_INIT_MODULE_GLOBALS给变量module_name##_globals_id分配了一个id,这个id是一个线程安全的资源id。而module_name##_globals_id不就是ZEND_DECLARE_MODULE_GLOBALS分配的变量吗!

globals_ctor是一个回调函数指针,这里不再多说。

6.4.4 知识点

我们再来看一下IAMNEW_G这个宏的定义

#ifdef ZTS //是否线程安全

#defineIAMNEW_G(v) TSRMG(iamnew_globals_id,zend_iamnew_globals *, v)

#else

#defineIAMNEW_G(v) (iamnew_globals.v)

#endif

----------------------------------------------------

TSRMG的定义为:

#define TSRMG(id, type, element) (((type) (*((void ***)tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)

tsrm_ls的定义为:

void ***tsrm_ls;

从宏整体的定义可以看出IAMNEW_G是取得变量的值。

其实IAMNEW_G就是取的全局变量的值。

如果你对TSRMG非常感兴趣的话,也可以看看这个宏具体的实现方式,不多对于新手来说的确有点困难。

7 使用php.ini里面的配置初始化全局变量

前面我们讲到了如何读取php.ini里面的配置,也讲到了如何初始化全局变量,这一节的任务也很明了,就是如何使用php.ini里面的配置初始化全局变量。这一节我们作为课堂任务来进行处理,不在单独解释。

8 参数接收

前面我们讲到了php扩展开发的大体架构,本节我们介绍扩展如何接收php脚本中传入的参数。

8.1 普通参数接收

任务:写一个扩展,输出php脚本传入的参数。例如php –r “echo hello(‘param test.’);” 将要输出param test.

首先我们建立一个paramtest的扩展,建议、编译、安装、测试过程不再赘述。

首先,我们需要建立一个函数叫做hello,建立的过程和之前是一样的,也不多说。

我们具体看一下,hello函数的实现,paramtest.c中hello函数的实现如下:

PHP_FUNCTION(hello)

{

char* str_hello;

int int_hello_str_length;

if(zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC,"s",

&str_hello,&int_hello_str_length)==FAILURE)

{

RETURN_NULL();

}

php_printf("%s",str_hello);

RETURN_TRUE;

}

我们先看一下zend_parse_parameters的函数定义:ZEND_NUM_ARGS() TSRMLS_CC其实是zend_parse_parameters的两个参数,具体可以查找这两个宏的定义,这两个宏主要是传入参数信息和保证线程安全。参数”s”其实是格式化字符串,也就是说这个参数告诉ZEND编译器,我可以接收的参数类型是什么样子的。Zend_parse_parameters的其他参数负责具体接收php脚本中函数变量的值。

请注意:我们在php脚本的函数中传入了一个参数,但是在zend_parse_parameters中却需要两个参数进行接收,给大家解释一下原因:我们在php脚本中传入的参数是字符串,对于C语言来说,PHP脚本传入字符串的长度是无法直接用函数strlen来进行获取的。原因是因为,我们在php脚本中可以传入\0,但是\0在C语言中是字符串结尾的意思。

在zend_parse_parameters中,格式化字符创(本例中是”s”)有很多,如果你想要接受多个参数的话,只要在格式化参数中加入相应的类型标示符,在参数中添加接受变量就可以了。例如,如果你的php函数需要传入两个变量,第一个变量为问候字符串,第二个参数为bool,表示是否输出,那么zend_parse_parameters函数可以这样写:zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC,"sb",&str_hello,&len,&is_output)

其中,格式化字符串可以列表如下:

PHP变量类型

代码

C扩展变量类型

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*

上表中有很多zval类型,这个类型我们下一节进行单独介绍。

另外一个需要注意的地方是,我们使用php_printf在C函数中进行输出,你能想到原因吗?前面我们已经讲到php既可以作为脚本在命令行运行,也可以通过web服务器,以单进程、多进程、多线程方式运行,如果我们在web服务器中将我们的信息输出到stdout中,会导致信息无法输出或者输出错误。给大家举个例子,我们可以再apache+php环境中写一个脚本,通过浏览器访问这个脚本,如果你使用printf输出字符串,那么浏览器将无法看到你的输出信息。但是如果你使用php_printf输出的话,浏览器就能显示你输出的信息。

8.2 可选参数接收

我们已经学会了如何接收普通参数,大家都知道php中还有可选参数,那么我们改怎样接收可选参数呢?(如果你不知道php可以使用可选参数的话,建议你先温习一下php的基础知识。)

这个很简单,我们直接上代码:

PHP_FUNCTION(hello)

{

char*str_hello;

int int_hello_str_length;

zend_bool is_output =0;

if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"s|b",&str_hello,

&int_hello_str_length,&is_output)== FAILURE)

{

RETURN_NULL();

}

if(is_output)

{

php_printf("%s", str_hello);

}

RETURN_TRUE;

}

很简单吧,如果你有可选参数,那么在格式化字符串中加入|就可以了,|后面的就是可选参数,其他的和普通变量的接收是一样的。

9 zval结构分析

9.1 初识zval

我们先来看一下zval的定义:

typedef union_zvalue_value {

long lval; /* long value */

double dval; /* double value */

struct {

char *val;

int len;

} str;

HashTable *ht; /* hash table value */

zend_object_value obj;

} zvalue_value;

struct _zval_struct {

/* Variable information*/

zvalue_value value; /* value */

zend_uint refcount__gc;

zend_uchar type; /*active type */

zend_uchar is_ref__gc;

};

typedef struct_zval_struct zval;

9.2 zval的创建和使用

我们先来看一段代码:

PHP_FUNCTION(hello)

{

zval* t;

ALLOC_INIT_ZVAL(t);

Z_TYPE_P(t)= IS_LONG;

Z_LVAL_P(t)

人气教程排行