English version download: PHP 5 Power Programming http://www.jb51.net/books/61020.html
One of the main reasons for PHP's success is that she has a large number of available extensions. No matter what needs web developers have, they are most likely to be found in PHP distribution packages. The PHP distribution package includes many extensions that support various databases, graphics file formats, compression, and XML technology extensions.
The introduction of extension API has made great progress in PHP3. The extension API mechanism allows the PHP development community to easily develop dozens of extensions. Now, two versions later, the API is still very similar to PHP3. The main idea of extensions is to hide the internal mechanisms of PHP and the scripting engine itself from extension writers as much as possible, and only require developers to be familiar with the API.
There are two reasons to write your own PHP extension. The first reason is: PHP needs to support a technology that it does not yet support. This usually involves wrapping some off-the-shelf C library to provide a PHP interface. For example, if a database called FooBase is launched on the market, you need to create a PHP extension to help you call FooBase's C function library from PHP. This work might be done by just one person and then shared by the entire PHP community (if you will). The second, less common reason is that you need to write some business logic for performance or functionality reasons.
If the above two reasons have nothing to do with you and you feel that you have no adventurous spirit, then you can skip this chapter.
This chapter teaches you how to write a relatively simple PHP extension, using some extension API functions. It contains enough information for most developers considering developing custom PHP extensions. One of the best ways to learn a programming course is to work on some extremely simple examples, which are the clues in this chapter. Once you understand the basics, you can enrich yourself on the Internet by reading documentation, source code, or participating in mailing list newsgroup discussions. Therefore, this chapter focuses on getting you started. Under UNIX a script called ext_skel is used to create the extension's skeleton. The skeleton information is obtained from a definition file that describes the extension's interface. So you need to use UNIX to build a skeleton. Windows developers can use Windows ext_skel_win32.php instead of ext_skel.
However, the instructions in this chapter for compiling PHP with the extensions you develop only refer to UNIX build systems. All explanations of the API in this chapter relate to extensions developed for UNIX and Windows.
When you finish reading this chapter, you will learn how to
• Build a simple business logic extension.
•Suggest a package extension for the C function library, especially some standard C file operation functions such as fopen()
Quick start
This section does not introduce some knowledge about the basic structure of the script engine, but goes directly to The extension's coding is being explained, so don't worry that you won't immediately get a feel for the extension as a whole. Suppose you are developing a website and need a function that repeats a string n times. The following is an example written in PHP:
Copy code The code is as follows:
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".
Suppose that for some strange reasons, you need to call this function from time to time, and you also need to pass a long string and a large value n to the function. This means that there is a considerable amount of string concatenation and memory reallocation in the script, which can significantly slow down script execution. If there was a function that could faster allocate a large and sufficient memory to store the result string, and then repeat $string n times, there would be no need to allocate memory on each loop iteration.
The first step in creating a function for an extension is to write a function definition file, which defines the function prototype provided by the extension. In this example, there is only one line of function prototype self_concat() to define the function:
Copy code The code is as follows:
string self_concat(string str, int n)
The general format of a function definition file is one function per line. You can define optional parameters and use a large number of PHP types, including: bool, float, int, array, etc.
Save it as myfunctions.def file in the PHP original code directory tree.
It’s time to run the function definition file by extending the skeleton constructor. The constructor script is called ext_skel and is placed in the ext/ directory of the PHP original code directory tree (README.EXT_SKEL in the PHP original code main directory provides more information). Assuming you save your function definitions in a file called myfunctions.def, and you want to name the extension myfunctions, run the following command to create the extension skeleton
Copy Code The code is as follows:
./ext_skel --extname=myfunctions --proto=myfunctions.de
This command creates a myfunctions/ directory under the ext/ directory. The first thing you might want to do is compile the skeleton so that you can write and test actual C code. There are two ways to compile extensions:
• As a loadable module or DSO (Dynamic Shared Object)
• Static compilation to PHP
PHP extension development map
Because the second method is easier to use, this chapter uses static compilation. If you are interested in compiling loadable extension modules, you can read the README.SELF-CONTAINED_EXTENSIONS file in the root directory of the PHP source code. In order for the extension to be compiled, the config.m4 file in the extension directory ext/myfunctions/ needs to be modified. The extension does not wrap any external C library, you need to add support for the --enable-myfunctions configuration switch to the PHP build system (the --with-extension switch is used for extensions that require the user to specify the path to the relevant C library). You can enable this configuration by removing the automatically generated comments on the following two lines.
Copy code The code is as follows:
./ext_skel --extname=myfunctions --proto=myfunctions.def
PHP_ARG_ENABLE(myfunctions, whether to enable myfunctions support,
[ --enable-myfunctions Include myfunctions support]
The only thing left now is to create the root of the PHP original code tree Run ./buildconf in the directory. This command will generate a new configuration script. You can check whether the new configuration options are included in the configuration file by viewing the ./configure –help output. Now, turn on your preferred configuration options. Reconfigure PHP with --enable-myfunctions. Last but not least, recompile PHP with make.
ext_skel should add two PHP functions to your extension skeleton: intended to be implemented. The self_concat() function and the confirm_myfunctions_compiled() function used to detect whether myfunctions are compiled into PHP can be removed after completing the PHP extension development.
Copy the code. The code is as follows:
print confirm_myfunctions_compiled("myextension");
?>
Run this The script will produce output similar to the following:
Copy the code The code is as follows:
"Congratulations! You have successfully modified ext/myfunctions
config.m4. Module myfunctions is now compiled into PHP.
In addition, the ext_skel script generates a script called myfunctions.php, which you can also use to verify whether the extension is successfully compiled. to PHP. It will list all the functions supported by the extension.
Now that you know how to compile the extension, it's time to actually study the self_concat() function.
Here is the skeleton structure generated by the ext_skel script. :
Copy code The code is as follows:
/* {{{ 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");
}
/* } }} */
There are some comments around the automatically generated PHP functions, which are used to automatically generate code documentation and code folding in editors such as vi and Emacs. The function itself is defined using the macro PHP_FUNCTION(), which can generate a function prototype suitable for the Zend engine. The logic itself is divided into semantic parts, taking the parameters of the calling function and the logic itself.
In order to obtain the parameters passed by the function, you can use the zend_parse_parameters() API function. The following is the prototype of the function:
Copy code The code is as follows:
zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, … );
第一个参数是传递给函数的参数个数。通常的做法是传给它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 |
To easily understand the meaning of the last few options, you need to know that zval is the Zend engine's value container[1]. Regardless of whether the variable is a boolean, string, or any other type, its information is always contained in a zval union. In this chapter we do not access zval directly, but operate it through some additional macros. Below is zval more or less in C so that we can better understand the code that follows.
Copy code The code is as follows:
typedef union _zval{
long lval;
double dval ;
struct {
char *val;
int len;
}str;
HashTable *ht;
zend_object_value obj;
}zval;
In our example, we call zend_parse_parameters() with basic types to get the values of function parameters in native C types instead of using zval containers.
In order for zend_parse_parameters() to change the value of the parameters passed to it and return the changed value, a reference needs to be passed. Take a closer look at self_concat():
Copy code The code is as follows:
if (zend_parse_parameters(argc TSRMLS_CC, " sl", &str, &str_len, &n) == FAILURE)return;
Note that the automatically generated code will detect the function's return value FAILUER (success is SUCCESS) to determine whether it is successful. If it is not successful, it returns immediately, and zend_parse_parameters() is responsible for triggering a warning message. Because the function is intended to receive a string l and an integer n, "sl" is specified as its type indicator. s requires two parameters, so we pass the reference char * and int (str and str_len) to the zend_parse_parameters() function. Whenever possible, remember to always use the string length str_len in your code to ensure that the function works in a binary-safe environment. Don't use strlen() and strcpy() unless you don't mind the function not working with binary strings. A binary string is a string containing nulls. Binary formats include image files, compressed files, executable files and many more. "l" only takes one argument, so we pass it a reference to n. Although for the sake of clarity, the C variable names generated by the skeleton script are the same as the parameter names in the function prototype definition file; this is not required, although in practice it is encouraged.
Back to the conversion rules. The following three calls to the self_concat() function cause str, str_len and n to get the same value:
Copy code The code is as follows:
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 points to the string "321", str_len equals 3, and n equals 5
When we write code to implement the connection character Before returning a string to a PHP function, we need to talk about two important topics: memory management and the API used to return function values from within PHP.
Memory Management
The PHP API for allocating memory from the heap is almost the same as the standard C API. When writing extensions, use the following API functions that correspond to C (so no need to explain):
Copy the code The code is as follows:
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) |
资源句柄。 |
Complete self_concat()
Now that you have learned how to allocate memory and return function values from PHP extension functions, we can complete the coding of self_concat():
Copy code The code is as follows:
/* {{{ 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 = '
resource file_open(string filename, string mode)
file_open() //Receive two strings (file name and mode) and return a file resource handle.
bool file_close(resource filehandle)
file_close() //Receive a resource handle and return true/false to indicate whether the operation is successful.
string file_read(resource filehandle, int size)
file_read() //Receive a resource handle and the total number of bytes read, and return the read string.
bool file_write(resource filehandle, string buffer)
file_write() //Receive a resource handle and the written string, and return true/false to indicate whether the operation was successful.
bool file_eof(resource filehandle)
file_eof() //Receive a resource handle and return true/false to indicate whether the end of the file has been reached.
Therefore, our function definition file - saved as myfile.def in the ext/ directory - contains the following:
Copy code The code is as follows:
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)
Next step, use The ext_skel script executes the following command in the ext./ original code directory:
Copy the code The code is as follows:
./ext_skel - -extname=myfile --proto=myfile.de
Then, follow the steps in the previous example for compiling the newly created script. You will get some compilation errors containing FETCH_RESOURCE() macro lines, so the skeleton script will not compile successfully. In order for the skeleton extension to compile smoothly, just comment out the error lines [3].
Resource
A resource is an abstract data structure that can hold any information. As mentioned earlier, this information usually includes, for example, file handles, database connection structures, and other complex types of data.
The main reason to use resources is because: resources are managed by a centralized queue, which can be automatically released when the PHP developer does not explicitly release it in the script.
For example, consider writing a script and calling mysql_connect() in the script to open a MySQL connection, but when the database connection resource is no longer used, mysql_close() is not called. In PHP, the resource mechanism can detect when the resource should be released, and then release the resource at the end of the current request or usually earlier. This gives a "bulletproof" mechanism to reduce memory leaks. Without such a mechanism, after several web requests, the web server may potentially leak many memory resources, causing the server to crash or error.
Register resource type
How to use resources? Zend Engine makes using resources very easy. The first thing you have to do is register the resource with the engine. Use this API function:
int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number)
This function returns a resource type id, which should be saved as a global variable in extension so that it can be passed to other resource APIs when necessary. ld: The function called when the resource is released. pld is used for persistent resources that persist across requests and will not be covered in this chapter. type_name is a string with a descriptive type name, and module_number is used internally by the engine. When we call this function, we only need to pass an already defined module_number variable.
Back to our example: we will add the following code to the original myfile.c file. This file includes the definition of the resource release function that is passed to the zend_register_list_destructors_ex() registration function (the resource release function should be added to the file early so that it is already defined when zend_register_list_destructors_ex() is called):
Copy code The code is as follows:
static void myfile_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){
FILE *fp = (FILE *) rsrc->ptr;
fclose(fp);
}
After adding the registration line to PHP_MINIT_FUNCTION(), it should look like the following code:
Copy code The code is as follows:
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;
}
l 注意到le_myfile是一个已经被ext_skel脚本定义好的全局变量。
PHP_MINIT_FUNCTION()是一个先于模块(扩展)的启动函数,是暴露给扩展的一部分API。下表提供可用函数简要的说明。
函数声明宏 |
语义 |
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);
参考表格对宏参数的解释
ZEND_REGISTER_RESOURCE 宏参数
宏参数 |
参数类型 |
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 |
文件函数
现在你知道了如何使用ZEND_REGISTER_RESOURCE()宏,并且准备好了开始编写file_open()函数。还有一个主题我们需要讲述。
当PHP运行在多线程服务器上,不能使用标准的C文件存取函数。这是因为在一个线程里正在运行的PHP脚本会改变当前工作目录,因此另外一个线程里的脚本使用相对路径则无法打开目标文件。为了阻止这种错误发生,PHP框架提供了称作VCWD (virtual current working directory 虚拟当前工作目录)宏,用来代替任何依赖当前工作目录的存取函数。这些宏与被替代的函数具备同样的功能,同时是被透明地处理。在某些没有标准C函数库平台的情况下,VCWD框架则不会得到支持。例如,Win32下不存在chown(),就不会有相应的VCWD_CHOWN()宏被定义。
VCWD列表
标准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函数
实现file_open()应该非常简单,看起来像下面的样子:
复制代码 代码如下:
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);
}
你可能会注意到资源注册宏的第一个参数return_value,可此地找不到它的定义。这个变量自动的被扩展框架定义为zval * 类型的函数返回值。先前讨论的、能够影响返回值的RETURN_LONG() 和RETVAL_BOOL()宏确实改变了return_value的值。因此很容易猜到程序注册了我们取得的文件指针fp,同时设置return_value为该注册资源。
访问资源 需要使用下面的宏访问资源(参看表对宏参数的解释)
复制代码 代码如下:
ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type);
ZEND_FETCH_RESOURCE 宏参数
参数 |
含义 |
rsrc |
资源值保存到的变量名。它应该和资源有相同类型。 |
rsrc_type |
rsrc的类型,用于在内部把资源转换成正确的类型 |
passed_id |
寻找的资源值(例如zval **) |
default_id |
如果该值不为-1,就使用这个id。用于实现资源的默认值。 |
resource_type_name |
资源的一个简短名称,用于错误信息。 |
resource_type |
注册资源的资源类型id |
使用这个宏,我们现在能够实现file_eof():
复制代码 代码如下:
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) <= 0) {
/* Return eof also if there was an error */
RETURN_TRUE;
}
RETURN_FALSE;
}
删除一个资源通常使用下面这个宏删除一个资源:
复制代码 代码如下:
int zend_list_delete(int id)
传递给宏一个资源id,返回SUCCESS或者FAILURE。如果资源存在,优先从Zend资源列队中删除,该过程中会调用该资源类型的已注册资源清理函数。因此,在我们的例子中,不必取得文件指针,调用fclose()关闭文件,然后再删除资源。直接把资源删除掉即可。
使用这个宏,我们能够实现file_close():
复制代码 代码如下:
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;
}
你肯定会问自己Z_RESVAL_P()是做什么的。当我们使用zend_parse_parameters()从参数列表中取得资源的时候,得到的是zval的形式。为了获得资源id,我们使用Z_RESVAL_P()宏得到id,然后把id传递给zend_list_delete()。
有一系列宏用于访问存储于zval值(参考表的宏列表)。尽管在大多数情况下zend_parse_parameters()返回与c类型相应的值,我们仍希望直接处理zval,包括资源这一情况。
Zval访问宏
宏 |
访问对象 |
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 |
Macros for accessing zval values
All macros have three forms: one accepts zval s, another accepts zval *s, and the last one accepts zval **s. The difference between them is that the first one has no suffix, zval * has the suffix _P (representing a pointer), and the last zval ** has the suffix _PP (representing two pointers).
Now, you have enough information to complete the file_read() and file_write() functions independently. Here is a possible implementation:
Copy code The code is as follows:
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] = '
表 ZEND_INIT_MODULE_GLOBALS 宏参数
参数 |
含义 |
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) |
你可以在myfile.c里看到如何使用构造函数和ZEND_INIT_MODULE_GLOBALS()宏的示例。
添加自定义INI指令
INI文件(php.ini)的实现使得PHP扩展注册和监听各自的INI条目。如果这些INI条目由php.ini、Apache的htaccess或其他配置方法来赋值,注册的INI变量总是更新到正确的值。整个INI框架有许多不同的选项以实现其灵活性。我们涉及一些基本的(也是个好的开端),借助本章的其他材料,我们就能够应付日常开发工作的需要。
通过在PHP_INI_BEGIN()/PHP_INI_END()宏之间的STD_PHP_INI_ENTRY()宏注册PHP INI指令。例如在我们的例子里,myfile.c中的注册过程应当如下:
复制代码 代码如下:
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()其他宏也能够使用,但这个宏是最常用的,可以满足大多数需要(参看表对宏参数的说明):
复制代码 代码如下:
STD_PHP_INI_ENTRY(name, default_value, modifiable, on_modify, property_name, struct_type, struct_ptr)
STD_PHP_INI_ENTRY 宏参数表
参数 |
含义 |
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。 |
最后,为了使自定义INI条目机制正常工作,你需要分别去掉PHP_MINIT_FUNCTION(myfile)中的REGISTER_INI_ENTRIES()调用和PHP_MSHUTDOWN_FUNCTION(myfile)中的UNREGISTER_INI_ENTRIES()的注释。
访问两个示例全局变量中的一个与在扩展里编写MYFILE_G(global_value) 和MYFILE_G(global_string)一样简单。
如果你把下面的两行放在php.ini中,MYFILE_G(global_value)的值会变为99。
复制代码 代码如下:
; php.ini – The following line sets the INI entry myfile.global_value to 99.myfile.global_value = 9
线程安全资源管理宏
现在,你肯定注意到以TSRM(线程安全资源管理器)开头的宏随处使用。这些宏提供给扩展拥有独自的全局变量的可能,正如前面提到的。
当编写PHP扩展时,无论是在多进程或多线程环境中,都是依靠这一机制访问扩展自己的全局变量。如果使用全局变量访问宏(例如MYFILE_G()宏),需要确保TSRM上下文信息出现在当前函数中。基于性能的原因,Zend引擎试图把这个上下文信息作为参数传递到更多的地方,包括PHP_FUNCTION()的定义。正因为这样,在PHP_FUNCTION()内当编写的代码使用访问宏(例如MYFILE_G()宏)时,不需要做任何特殊的声明。然而,如果PHP函数调用其他需要访问全局变量的C函数,要么把上下文作为一个额外的参数传递给C函数,要么提取上下文(要慢点)。
在需要访问全局变量的代码块开头使用TSRMLS_FETCH()来提取上下文。例如:
复制代码 代码如下:
void myfunc(){
TSRMLS_FETCH();
MYFILE_G(myglobal) = 2;
}
如果希望让代码更加优化,更好的办法是直接传递上下文给函数(正如前面叙述的,PHP_FUNCTION()范围内自动可用)。可以使用TSRMLS_C(C表示调用Call)和TSRMLS_CC(CC边式调用Call和逗号Comma)宏。前者应当用于仅当上下文作为一个单独的参数,后者应用于接受多个参数的函数。在后一种情况中,因为根据取名,逗号在上下文的前面,所以TSRMLS_CC不能是第一个函数参。
在函数原形中,可以分别使用TSRMLS_D和TSRMLS_DC宏声名正在接收上下文。
下面是前一例子的重写,利用了参数传递上下文。
复制代码 代码如下:
void myfunc(TSRMLS_D){
MYFILE_G(myglobal) = 2;
}
PHP_FUNCTION(my_php_function)
{
…
myfunc(TSRMLS_C);
…
}
~
总 结
现在,你已经学到了足够的东西来创建自己的扩展。本章讲述了一些重要的基础来编写和理解PHP扩展。Zend引擎提供的扩展API相当丰富,使你能够开发面向对象的扩展。几乎没有文档谈几许多高级特性。当然,依靠本章所学的基础知识,你可以通过浏览现有的原码学到很多。
更多关于信息可以在PHP手册的扩展PHP章节http://www.php.net/manual/en/zend.php中找到。另外,你也可以考虑加入PHP开发者邮件列表internals@ lists.php.net,该邮件列表围绕开发PHP 本身。你还可以查看一下新的扩展生成工具——PECL_Gen(http://pear.php.net/package/PECL_Gen),这个工具正在开发之中,比起本章使用的ext_skel有更多的特性。
此外你还可以关注风雪之隅, 会有更多相关知识更新.
词汇表
binary safe |
二进制安全 |
context |
上下文 |
extensions |
扩展 |
entry |
条目 |
skeleton |
骨架 |
Thread-Safe Resource Manager TSRM |
线程安全资源管理器 |
[1] Please refer to what the translator wrote
[2] Translator: You can use the phpcli program to execute php files in the console.
[3] Translator: You can see that the generated FETCH_RESOURCE() macro parameters are some '???'.
http://www.bkjia.com/PHPjc/325828.htmlwww.bkjia.comtruehttp: //www.bkjia.com/PHPjc/325828.htmlTechArticleEnglish version download: PHP 5 Power Programming http://www.jb51.net/books/61020.html PHP One of the main reasons for this success is the large number of expansions she has available. Web developers no matter what...