英文版下载: PHP 5 Power Programming http://www.jb51.net/books/61020.html
PHP取得成功的一个主要原因之一是她拥有大量的可用扩展。web开发者无论有何种需求,这种需求最有可能在PHP发行包里找到。PHP发行包包括支持各种数据库,图形文件格式,压缩,XML技术扩展在内的许多扩展。
扩展API的引入使PHP3取得了巨大的进展,扩展API机制使PHP开发社区很容易的开发出几十种扩展。现在,两个版本过去了,API仍然和PHP3时的非常相似。扩展主要的思想是:尽可能的从扩展编写者那里隐藏PHP的内部机制和脚本引擎本身,仅仅需要开发者熟悉API。
有两个理由需要自己编写PHP扩展。第一个理由是:PHP需要支持一项她还未支持的技术。这通常包括包裹一些现成的C函数库,以便提供PHP接口。例如,如果一个叫FooBase的数据库已推出市场,你需要建立一个PHP扩展帮助你从PHP里调用FooBase的C函数库。这个工作可能仅由一个人完成,然后被整个PHP社区共享(如果你愿意的话)。第二个不是很普遍的理由是:你需要从性能或功能的原因考虑来编写一些商业逻辑。
如果以上的两个理由都和你没什么关系,同时你感觉自己没有冒险精神,那么你可以跳过本章。
本章教你如何编写相对简单的PHP扩展,使用一部分扩展API函数。对于大多数打算开发自定义PHP扩展开发者而言,它含概了足够的资料。学习一门编程课程的最好方法之一就是动手做一些极其简单的例子,这些例子正是本章的线索。一旦你明白了基础的东西,你就可以在互联网上通过阅读文挡、原代码或参加邮件列表新闻组讨论来丰富自己。因此,本章集中在让你如何开始的话题。在UNIX下一个叫ext_skel的脚本被用于建立扩展的骨架,骨架信息从一个描述扩展接口的定义文件中取得。因此你需要利用UNIX来建立一个骨架。Windows开发者可以使用Windows ext_skel_win32.php代替ext_skel。
然而,本章关于用你开发的扩展编译PHP的指导仅涉及UNIX编译系统。本章中所有的对API的解释与UNIX和Windows下开发的扩展都有联系。
当你阅读完这章,你能学会如何
•建立一个简单的商业逻辑扩展。
•建议个C函数库的包裹扩展,尤其是有些标准C文件操作函数比如fopen()
快速开始
本节没有介绍关于脚本引擎基本构造的一些知识,而是直接进入扩展的编码讲解中,因此不要担心你无法立刻获得对扩展整体把握的感觉。假设你正在开发一个网站,需要一个把字符串重复n次的函数。下面是用PHP写的例子:
复制代码 代码如下:
function self_concat($string, $n){
$result = "";
for($i = 0; $i $result .= $string;
}
return $result;
}
self_concat("One", 3) returns "OneOneOne".
self_concat("One", 1) returns "One".
复制代码 代码如下:
string self_concat(string str, int n)
复制代码 代码如下:
./ext_skel --extname=myfunctions --proto=myfunctions.de
复制代码 代码如下:
./ext_skel --extname=myfunctions --proto=myfunctions.def
PHP_ARG_ENABLE(myfunctions, whether to enable myfunctions support,
[ --enable-myfunctions Include myfunctions support]
复制代码 代码如下:
print confirm_myfunctions_compiled("myextension");
?>
复制代码 代码如下:
"Congratulations! You have successfully modified ext/myfunctions
config.m4. Module myfunctions is now compiled into PHP.
复制代码 代码如下:
/* {{{ 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(int num_args TSRMLS_DC, char *type_spec, …);
类型指定符 | 对应的C类型 | 描述 |
l | long | 符号整数 |
d | double | 浮点数 |
s | char *, int | 二进制字符串,长度 |
b | zend_bool | 逻辑型(1或0) |
r | zval * | 资源(文件指针,数据库连接等) |
a | zval * | 联合数组 |
o | zval * | 任何类型的对象 |
O | zval * | 指定类型的对象。需要提供目标对象的类类型 |
z | zval * | 无任何操作的zval |
复制代码 代码如下:
typedef union _zval{
long lval;
double dval;
struct {
char *val;
int len;
}str;
HashTable *ht;
zend_object_value obj;
}zval;
复制代码 代码如下:
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)return;
复制代码 代码如下:
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
复制代码 代码如下:
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);
设置返回值并且结束函数 | 设置返回值 | 宏返回类型和参数 |
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) | 资源句柄。 |
复制代码 代码如下:
/* {{{ 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;
char *result; /* Points to resulting string */
char *ptr; /* Points at the next location we want to copy to */
int result_length; /* Length of resulting string */
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
/* Calculate length of result */
result_length = (str_len * n);
/* Allocate memory for result */
result = (char *) emalloc(result_length + 1);
/* Point at the beginning of the result */
ptr = result;
while (n--) {
/* Copy str to the result */
memcpy(ptr, str, str_len);
/* Increment ptr to point at the next position we want to write to */
ptr += str_len;
}
/* Null terminate the result. Always null-terminate your strings
even if they are binary strings */
*ptr = '\0';
/* Return result to the scripting engine without duplicating it*/
RETURN_STRINGL(result, result_length, 0);
}
/* }}} */
复制代码 代码如下:
for ($i = 1; $i print self_concat("ThisIsUseless", $i);
print "\n";
}
?>
复制代码 代码如下:
ThisIsUseless
ThisIsUselessThisIsUseless
ThisIsUselessThisIsUselessThisIsUseles
复制代码 代码如下:
FILE *fopen(const char *path, const char *mode);
int fclose(FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int feof(FILE *stream);
复制代码 代码如下:
resource file_open(string filename, string mode)
file_open() //接收两个字符串(文件名和模式),返回一个文件的资源句柄。
bool file_close(resource filehandle)
file_close() //接收一个资源句柄,返回真/假指示是否操作成功。
string file_read(resource filehandle, int size)
file_read() //接收一个资源句柄和读入的总字节数,返回读入的字符串。
bool file_write(resource filehandle, string buffer)
file_write() //接收一个资源句柄和被写入的字符串,返回真/假指示是否操作成功。
bool file_eof(resource filehandle)
file_eof() //接收一个资源句柄,返回真/假指示是否到达文件的尾部。
复制代码 代码如下:
resource file_open(string filename, string mode)
bool file_close(resource filehandle)
string file_read(resource filehandle, int size)
bool file_write(resource filehandle, string buffer)
bool file_eof(resource filehandle)
复制代码 代码如下:
./ext_skel --extname=myfile --proto=myfile.de
复制代码 代码如下:
static void myfile_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){
FILE *fp = (FILE *) rsrc->ptr;
fclose(fp);
}
复制代码 代码如下:
PHP_MINIT_FUNCTION(myfile){
/* If you have INI entries, uncomment these lines
ZEND_INIT_MODULE_GLOBALS(myfile, php_myfile_init_globals,NULL);
REGISTER_INI_ENTRIES();
*/
le_myfile = zend_register_list_destructors_ex(myfile_dtor,NULL,"standard-c-file", module_number);
return SUCCESS;
}
函数声明宏 | 语义 |
PHP_MINIT_FUNCTION() | 当PHP被装载时,模块启动函数即被引擎调用。这使得引擎做一些例如资源类型,注册INI变量等的一次初始化。 |
PHP_MSHUTDOWN_FUNCTION() | 当PHP完全关闭时,模块关闭函数即被引擎调用。通常用于注销INI条目 |
PHP_RINIT_FUNCTION() | 在每次PHP请求开始,请求前启动函数被调用。通常用于管理请求前逻辑。 |
PHP_RSHUTDOWN_FUNCTION() | 在每次PHP请求结束后,请求前关闭函数被调用。经常应用在清理请求前启动函数的逻辑。 |
PHP_MINFO_FUNCTION() | 调用phpinfo()时模块信息函数被呼叫,从而打印出模块信息。 |
新建和注册新资源 我们准备实现file_open()函数。当我们打开文件得到一个FILE *,我们需要利用资源机制注册它。下面的主要宏实现注册功能:
复制代码 代码如下:
ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type);
宏参数 | 参数类型 |
rsrc_result | zval *, which should be set with the registered resource information. zval * 设置为已注册资源信息 |
rsrc_pointer | Pointer to our resource data. 资源数据指针 |
rsrc_type | The resource id obtained when registering the resource type. 注册资源类型时获得的资源id |
标准C库 | VCWD宏 |
getcwd() | VCWD_GETCWD() |
fopen() | VCWD_FOPEN |
open() | VCWD_OPEN() //用于两个参数的版本 |
open() | VCWD_OPEN_MODE() //用于三个参数的open()版本 |
creat() | VCWD_CREAT() |
chdir() | VCWD_CHDIR() |
getwd() | VCWD_GETWD() |
realpath() | VCWD_REALPATH() |
rename() | VCWD_RENAME() |
stat() | VCWD_STAT() |
lstat() | VCWD_LSTAT() |
unlink() | VCWD_UNLINK() |
mkdir() | VCWD_MKDIR() |
rmdir() | VCWD_RMDIR() |
opendir() | VCWD_OPENDIR() |
popen() | VCWD_POPEN() |
access() | VCWD_ACCESS() |
utime() | VCWD_UTIME() |
chmod() | VCWD_CHMOD() |
chown() | VCWD_CHOWN() |
复制代码 代码如下:
PHP_FUNCTION(file_open){
char *filename = NULL;
char *mode = NULL;
int argc = ZEND_NUM_ARGS();
int filename_len;
int mode_len;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename,&filename_len, &mode, &mode_len) == FAILURE) {
return;
}
fp = VCWD_FOPEN(filename, mode);
if (fp == NULL) {
RETURN_FALSE;
}
ZEND_REGISTER_RESOURCE(return_value, fp, le_myfile);
}
复制代码 代码如下:
ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type);
参数 | 含义 |
rsrc | 资源值保存到的变量名。它应该和资源有相同类型。 |
rsrc_type | rsrc的类型,用于在内部把资源转换成正确的类型 |
passed_id | 寻找的资源值(例如zval **) |
default_id | 如果该值不为-1,就使用这个id。用于实现资源的默认值。 |
resource_type_name | 资源的一个简短名称,用于错误信息。 |
resource_type | 注册资源的资源类型id |
复制代码 代码如下:
PHP_FUNCTION(file_eof){
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) ==FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c-file",le_myfile);
if (fp == NULL){
RETURN_FALSE;
}
if (feof(fp) /* Return eof also if there was an error */
RETURN_TRUE;
}
RETURN_FALSE;
}
复制代码 代码如下:
int zend_list_delete(int id)
复制代码 代码如下:
PHP_FUNCTION(file_close){
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) {
return;
}
if (zend_list_delete(Z_RESVAL_P(filehandle)) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
宏 | 访问对象 | C 类型 |
Z_LVAL, Z_LVAL_P, Z_LVAL_PP | 整型值 | long |
Z_BVAL, Z_BVAL_P, Z_BVAL_PP | 布尔值 | zend_bool |
Z_DVAL, Z_DVAL_P, Z_DVAL_PP | 浮点值 | double |
Z_STRVAL, Z_STRVAL_P, Z_STRVAL_PP | 字符串值 | char * |
Z_STRLEN, Z_STRLEN_P, Z_STRLEN_PP | 字符串长度值 | int |
Z_RESVAL, Z_RESVAL_P,Z_RESVAL_PP | 资源值 | long |
Z_ARRVAL, Z_ARRVAL_P, Z_ARRVAL_PP | 联合数组 | HashTable * |
Z_TYPE, Z_TYPE_P, Z_TYPE_PP | Zval类型 | Enumeration (IS_NULL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_BOOL, IS_RESOURCE) |
Z_OBJPROP, Z_OBJPROP_P, Z_OBJPROP_PP | 对象属性hash(本章不会谈到) | HashTable * |
Z_OBJCE, Z_OBJCE_P, Z_OBJCE_PP | 对象的类信息 | zend_class_entry |
所有的宏都有三种形式:一个是接受zval s,另外一个接受zval *s,最后一个接受zval **s。它们的区别是在命名上,第一个没有后缀,zval *有后缀_P(代表一个指针),最后一个 zval **有后缀_PP(代表两个指针)。
现在,你有足够的信息来独立完成 file_read()和 file_write()函数。这里是一个可能的实现:
复制代码 代码如下:
PHP_FUNCTION(file_read){
int argc = ZEND_NUM_ARGS();
long size;
zval *filehandle = NULL;
FILE *fp;
char *result;
size_t bytes_read;
if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle,&size) == FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
result = (char *) emalloc(size+1);
bytes_read = fread(result, 1, size, fp);
result[bytes_read] = '\0';
RETURN_STRING(result, 0);
}
PHP_FUNCTION(file_write){
char *buffer = NULL;
int argc = ZEND_NUM_ARGS();
int buffer_len;
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "rs", &filehandle,&buffer, &buffer_len) == FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-cfile", le_myfile);
if (fwrite(buffer, 1, buffer_len, fp) != buffer_len) {
RETURN_FALSE;
}
RETURN_TRUE;
}
复制代码 代码如下:
$fp_in = file_open("test.txt", "r") or die("Unable to open input file\n");
$fp_out = file_open("test.txt.new", "w") or die("Unable to open output file\n");
while (!file_eof($fp_in)) {
$str = file_read($fp_in, 1024);
print($str);
file_write($fp_out, $str);
}
file_close($fp_in);
file_close($fp_out);
?>
复制代码 代码如下:
ZEND_BEGIN_MODULE_GLOBALS(myfile)
int global_value;
char *global_string;
ZEND_END_MODULE_GLOBALS(myfile)
复制代码 代码如下:
ZEND_DECLARE_MODULE_GLOBALS(myfile)
复制代码 代码如下:
ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)
参数 | 含义 |
module_name | 与传递给ZEND_BEGIN_MODULE_GLOBALS()宏相同的扩展名称。 |
globals_ctor | 构造函数指针。在myfile扩展里,函数原形与void php_myfile_init_globals(zend_myfile_globals *myfile_globals)类似 |
globals_dtor | 析构函数指针。例如,php_myfile_init_globals(zend_myfile_globals *myfile_globals) |
复制代码 代码如下:
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("myfile.global_value", "42", PHP_INI_ALL, OnUpdateInt, global_value, zend_myfile_globals, myfile_globals)
STD_PHP_INI_ENTRY("myfile.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_myfile_globals, myfile_globals)
PHP_INI_END()
复制代码 代码如下:
STD_PHP_INI_ENTRY(name, default_value, modifiable, on_modify, property_name, struct_type, struct_ptr)
参数 | 含义 |
name | INI条目名 |
default_value | 如果没有在INI文件中指定,条目的默认值。默认值始终是一个字符串。 |
modifiable | 设定在何种环境下INI条目可以被更改的位域。可以的值是: • PHP_INI_SYSTEM. 能够在php.ini或http.conf等系统文件更改 • PHP_INI_PERDIR. 能够在 .htaccess中更改 • PHP_INI_USER. 能够被用户脚本更改 • PHP_INI_ALL. 能够在所有地方更改 |
on_modify | 处理INI条目更改的回调函数。你不需自己编写处理程序,使用下面提供的函数。包括: • OnUpdateInt • OnUpdateString • OnUpdateBool • OnUpdateStringUnempty • OnUpdateReal |
property_name | 应当被更新的变量名 |
struct_type | 变量驻留的结构类型。因为通常使用全局变量机制,所以这个类型自动被定义,类似于zend_myfile_globals。 |
struct_ptr | 全局结构名。如果使用全局变量机制,该名为myfile_globals。 |
复制代码 代码如下:
; php.ini – The following line sets the INI entry myfile.global_value to 99.myfile.global_value = 9
复制代码 代码如下:
void myfunc(){
TSRMLS_FETCH();
MYFILE_G(myglobal) = 2;
}
复制代码 代码如下:
void myfunc(TSRMLS_D){
MYFILE_G(myglobal) = 2;
}
PHP_FUNCTION(my_php_function)
{
…
myfunc(TSRMLS_C);
…
}
~
binary safe | 二进制安全 |
context | 上下文 |
extensions | 扩展 |
entry | 条目 |
skeleton | 骨架 |
Thread-Safe Resource Manager TSRM | 线程安全资源管理器 |