buildconf 处理的配置文件 config.m4 包含了所有在配置过程中所执行的指令。这些指令诸如包含测试包含所需的外部文件,像头文件、库文件等等。PHP 定义了一系列处理这类情况的宏,其中最常用的我们已经在“表3.18 config.m4 中的 M4 宏”列了出来。


表3.18 config.m4 中的 M4 宏

说明
AC_MSG_CHECKING(message) 在执行 configure 命令时输出“checking <message>”等信息。
AC_MSG_RESULT(value) 取得 AC_MSG_CHECKING 的执行结果,一般情况下 value 应为 yesno
AC_MSG_ERROR(message) 在执行 configure 命令时输出一条错误消息 message 并中止脚本的执行。
AC_DEFINE(name,value,description)

php_config.h 添加一行定义:

#define name value // description

(这对模块的条件编译很有用。)

AC_ADD_INCLUDE(path) 添加一条编译器的包含路径,比如用于模块需要为头文件添加搜索路径。
AC_ADD_LIBRARY_WITH_PATH(libraryname,librarypath) 指定一个库的连接路径。
AC_ARG_WITH(modulename,description,unconditionaltest,conditionaltest) 这是一款比较强大的宏,用于将模块的描述 description 添加到“configure –help”命令的输出里面。PHP 会检查当前执行的 configure 脚本里面有没有–with-<modulename> 这个选项。 如果有则执行 unconditionaltest 语句(比如 –with-myext=yes 等), 此时,选项的值会被包含在 $withval 变量里面。否则就执行 conditionaltest 语句。
PHP_EXTENSION(modulename, [shared]) 这个是配置你的扩展时 PHP 必定调用的一个宏。你可以在模块名后面提供第二个参数,用来表明是否将其编译为动态共享模块。这会导致在编译时为你的源码提供一个 COMPILE_DL_<modulename> 的定义。

现在你已经掌握了很多关于 PHP 的知识了。你已经知道了如何创建一个动态加载的模块或被静态连接的扩展。你还知道了在 PHP 和 Zend 的内部变量是如何储存的,以及如何创建和访问这些变量。另外你也知道了很多诸如输出信息文本、自动将变量引入符号表等一系列工具函数的应用。

尽管这一章常常有点“参考”的意味,但我们还是希望它能给你一些关于如何开始编写自己的扩展这方面的知识。限于篇幅,我们不得不省略了很多东西。我们建议你花些时间仔细研究一下它的头文件和一些模块(尤其是 ext/standard 目录下的一些文件以及 MySQL 模块,看一下这些众所周知的函数究竟是怎么实现的),看一下别人是怎么使用这些 API 函数的,尤其是那些本章没有提到的那些函数。

PHP4 重写了对初始化文件的支持。现在你可以直接在代码中指定一些初始化选项,然后在运行时读取和改变这些选项值,甚至还可以在选项值改变时接到相关通知。

如果想要为你的模块创建一个 .ini 文件的配置节,可以使用宏 PHP_INI_BEGIN() 来标识这个节的开始,并用 PHP_INI_END() 表示该配置节已经结束。然后在两者之间我们用 PHP_INI_ENTRY() 来创建具体的配置项。

PHP_INI_BEGIN()
PHP_INI_ENTRY(”first_ini_entry”,  ”has_string_value”, PHP_INI_ALL, NULL)
PHP_INI_ENTRY(”second_ini_entry”, “2″,                PHP_INI_SYSTEM, OnChangeSecond)
PHP_INI_ENTRY(”third_ini_entry”,  ”xyz”,              PHP_INI_USER, NULL)
PHP_INI_END()

PHP_INI_ENTRY() 总共接收 4 个参数:配置项名称、初始值、改变这些值所需的权限以及在值改变时用于接收通知的函数句柄。配置项名称和初始值必须是一个字符串,即使它们是一个整数。

更改这些值所需的权限可以划分为三种:PHP_INI_SYSTEM 只允许在 php.ini 中改变这些值;PHP_INI_USER 允许用户在运行时通过像 .htaccess 这样的附加文件来重写其值;而 PHP_INI_ALL 则允许随意更改。其实还有第四种权限:PHP_INI_PERDIR,不过我们还暂时不能确定它有什么影响。(本段关于这几种权限的说明与手册中《附录G php.ini 配置选项》一节的描述略有出入。根据译者自己查到的资料,相比之下还是《附录G php.ini 配置选项》更为准确些。译注)

第四个参数是初始值被改变时接收通知的函数句柄。一旦某个初始值被改变,那么相应的函数就会被调用。这个函数我们可以用宏 PHP_INI_MH 来定义:

PHP_INI_MH(OnChangeSecond);             // handler for ini-entry "second_ini_entry"
// specify ini-entries here
PHP_INI_MH(OnChangeSecond)
{
   zend_printf(”Message caught, our ini entry has been changed to %s<br>”, new_value);
   return(SUCCESS);
}

改变后的新值将会以字符串的形式并通过一个名为 new_value 变量传递给函数。要是再注意一下 PHP_INI_MH 的定义就会发现,我们实际上用到了不少参数:

#define PHP_INI_MH(name) int name(php_ini_entry *entry, char *new_value,
                                 uint new_value_length, void *mh_arg1,
                                 void *mh_arg2, void *mh_arg3)

这些定义都可以在 php_ini.h 文件里找到。可以发现,我们的通知接收函数可以访问整个配置项、改变后的新值以及它的长度和其他三个可选参数。这几个可选参数可以通过 PHP_INI_ENTRY1(携带一个附加参数)、PHP_INI_ENTRY2(携带两个附加参数)、PHP_INI_ENTRY3(携带三个附加参数)等宏来加以指定。

关于值改变的通知函数应该被用来本地缓存一些初始花选项以便可以更快地对其访问或被用来从事一个值发生改变时所要求完成的任务。比如要是一个模块对一个主机常量进行了连接,而这时有人改变了主机名,那么就需要自动地关闭原来的连接,并尝试进行新的连接。

可以使用“表3.17 PHP 中用以访问初始化配置项的宏”来访问初始化配置项:


表3.17 PHP 中用以访问初始化配置项的宏

说明
INI_INT(name) 将配置项 name 的当前值以长整数返回。
INI_FLT(name) 将配置项 name 的当前值以双精度浮点数返回。
INI_STR(name) 将配置项 name 的当前值以字符串返回。 注意:这个字符串不是复制过的字符串,而是直接指向了内部数据。如果你需要进行进一步的访问的话,那就需要再进行复制一下。
INI_BOOL(name) 将配置项 name 的当前值以布尔值返回。(返回值被定义为 zend_bool,也就是说是一个 unsigned char)。
INI_ORIG_INT(name) 将配置项 name 的初始值以长整型数返回。
INI_ORIG_FLT(name) 将配置项 name 的初始值以双精度浮点数返回。
INI_ORIG_STR(name) 将配置项 name 的初始值以字符串返回。 注意:这个字符串不是复制过的字符串,而是直接指向了内部数据。如果你需要进行进一步的访问的话,那就需要再进行复制一下。
INI_ORIG_BOOL(name) 将配置项 name 的初始值以布尔值返回。(返回值被定义为 zend_bool,也就是说是一个 unsigned char)。

最后,你还得把整个初始化配置项引入 PHP。这项工作可以在模块的起始/结束函数中使用宏 REGISTER_INI_ENTRIES() 和 UNREGISTER_INI_ENTRIES() 来搞定。

ZEND_MINIT_FUNCTION(mymodule)
{
   REGISTER_INI_ENTRIES();
}
ZEND_MSHUTDOWN_FUNCTION(mymodule)
{
   UNREGISTER_INI_ENTRIES();
}

PHP 还允许你在你的模块里面调用一些一些用户定义的函数,这样在实现某些回调机制(比如在做一些数组的轮循(array walking)、搜索或设计一些简单的事件驱动的程序时)时会很方便。

我们可以通过调用 call_user_function_ex() 来调用用户函数。它需要你即将访问函数表的指针、这个对象的指针(假如你访问的是类的一个方法的话),函数名、返回值、参数个数、具体的参数数组和一个是否需要进行 zval 分离的标识(这个函数原型已经“过时”了,至少是从 PHP 4.2 开始这个函数就追加了一个 HashTable *symbol_table 参数。下面所列举的函数原型更像是 call_user_function () 的声明。译注)。

ZEND_API int call_user_function_ex(HashTable *function_table, zval *object,
zval *function_name, zval **retval_ptr_ptr,
int param_count, zval **params[],
int no_separation);

需要注意的是你不必同时指定 function_tableobject 这两个参数,只需要指定其中一个就行了。不过如果你想调用一个方法的话,那你就必须提供一个包含此方法的对象。这时 call_user_function() 会自动将函数表设置为当前这个对象的函数表。而对于其他情况,只需要设定一下 function_table 而把 object 设为 NULL 就行了。

一般情况下,默认的函数表是包含有所有函数的“根”函数表。这个函数表是编译器全局变量的一部分,你可以通过 CG() 宏来访问它。如果想把编译器全局变量引入你的函数,只需先执行一下 TSRMLS_FETCH 宏就可以了。

而调用的函数名是保存在一个 zval 容器内的。猛一下你可能会感到好奇,但其实这是很合乎逻辑的。想想看,既然我们在脚本中的大部分时间都是在接收一个函数名作为参数,并且这个参数还是被转换成(或被包含在)一个 zval 容器。那还不如现在就直接把这个 zval 容器传送给函数,只是这个 zval 容器的类型必须为 IS_STRING

下一个参数是返回值 return_value 的指针。这个容器的空间函数会自动帮你申请,所以我们无需手动申请,但在事后这个容器空间的销毁释放工作得由我们自己(使用 zval_dtor())来做。

跟在 return_value 后面的是一个标识参数个数的整数和一个包含具体参数的数组。最后一个参数 no_separation 指明了函数是否禁止进行 zval 分离操作。这个参数应该总是设为 0,因为如果设为 1 的话那这个函数会节省一些空间但要是其中任何一个参数需要做 zval 分离时都会导致操作失败。

“例3.15 调用用户函数”向我们展示如何去调用一个脚本中的用户函数。这段代码调用了一个我们模块所提供的 call_userland() 函数。模块中的 call_userland() 函数会调用脚本中一个名为它的参数的用户函数,并且将这个用户函数的返回值直接作为自己的返回值返回脚本。另外你可能注意到了我们在最后调用了析构函数。这个操作或许没有太大必要(因为这些值都应该是分离过的,对它们的赋值将会很安全),但这么做总没有什么坏处,说不定在某个关键时刻它成为我们的一道“免死金牌”。:D


例3.15 调用用户函数

zval **function_name;
zval *retval;
if((ZEND_NUM_ARGS() != 1) || (zend_get_parameters_ex(1, &function_name) != SUCCESS))
{
   WRONG_PARAM_COUNT;
}
if((*function_name)->type != IS_STRING)
{
   zend_error(E_ERROR, “Function requires string argument”);
}
TSRMSLS_FETCH();
if(call_user_function_ex(CG(function_table), NULL, *function_name, &retval, 0, NULL, 0) != SUCCESS)
{
   zend_error(E_ERROR, “Function call failed”);
}
zend_printf(”We have %i as type\n”, retval->type);
*return_value = *retval;
zval_copy_ctor(return_value);
zval_ptr_dtor(&retval);

调用脚本:

<?php
dl(”call_userland.so”);
function test_function()
{
   echo “We are in the test function!\n”;
   return ‘hello’;
}
$return_value = call_userland(”test_function”);
echo “Return value: ‘$return_value’”;
?>

上例将输出:

We are in the test function! We have 3 as type Return value: ‘hello’

启动函数和关闭函数会在模块的(载入时)初始化和(卸载时)反初始化时被调用,而且只调用这一次。正如我们在本章前面(见 Zend 模块描述块的说明)所提到的,它们是模块和请求启动和关闭时所发生的事件。

模块启动/关闭函数会在模块加载和卸载时被调用。请求启动/关闭函数会在每次处理一个请求时(也就是在执行一个脚本文件时)被调用。

对于动态加载的扩展而言,模块和请求的启动函数与模块和请求的关闭函数都是同时发生的(严格来说模块启动函数是先于请求启动函数被调用的,译注)。

可以用某些宏来声明和实现这些函数,详情请参阅前面的关于“Zend 模块声明”的讨论。

就像我们在脚本中使用 print() 函数一样,我们也经常需要从扩展向输出流输出一些信息。在这方面-比如输出警告信息、phpinfo() 中对应的信息等一般性任务-PHP 也为我们提供了一系列函数。这一节我们就来详细地讨论一下它们。

zend_printf()

zend_printf() 功能跟 printf() 差不多, 唯一不同的就是它是向 Zend 的输出流提供信息。

zend_error()

zend_error() 用于创建一个错误信息。这个函数接收两个参数:第一个是错误类型(见 zend_error.h),第二个是错误的提示消息。

zend_error(E_WARNING, "This function has been called with empty arguments");

“表3.16 Zend 预定义的错误信息类型” 列出了一些可能的值(在 PHP 5.0 及以上版本中又增加了一些错误类型,可参见 zend_error.h,译注)。这些值也可以用在 php.ini 里面,这样你的错误信息将会依照 php.ini 里面的设置,根据不同的错误类型而被选择性地记录。

表3.16 Zend 预定义的错误信息类型
错误类型 说明
E_ERROR 抛出一个错误,然后立即中止脚本的执行。
E_WARNING 抛出一个一般性的警告。脚本会继续执行。
E_NOTICE 抛出一个通知,脚本会继续执行。注意: 默认情况下 php.ini 会关闭显示这种错误。
E_CORE_ERROR 抛出一个 PHP 内核错误。通常情况下这种错误类型不应该被用户自己编写的模块所引用。
E_COMPILE_ERROR 抛出一个编译器内部错误。通常情况下这种错误类型不应该被用户自己编写的模块所引用。
E_COMPILE_WARNING 抛出一个编译器内部警告。通常情况下这种错误类型不应该被用户自己编写的模块所引用。

图3.3 在浏览器中显示警告信息

在浏览器中显示警告信息

向 phpinfo() 中输出信息

在创建完一个模块之后,你可能就会想往 phpinfo() 里面添加一些关于你自己模块的一些信息了(默认是只显示你的模块名)。PHP 允许你用 ZEND_MINFO() 函数向 phpinfo() 里面添加一段你自己模块的信息。这个函数应该被放在模块描述块(见前文)部分,这样在脚本调用 phpinfo() 时模块的这个函数就会被自动调用。

如果你指定了 ZEND_MINFO 函数,phpinfo() 会自动打印一个小节,这个小节的头部就是你的模块名。其余的信息就需要你自己去指定一下格式并输出了。

一般情况下,你需要先调用一下 php_info_print_table_start(),然后再调用php_info_print_table_header()php_info_print_table_row() 这两个标准函数来打印表格具体的行列信息。这两个函数都以表格的列数(整数)和相应列的内容(字符串)作为参数。最后使用 php_info_print_table_end() 来结束打印表格。“例3.13 源代码及其 在 phpinfo() 函数中的屏幕显示”向我们展示了某个样例和它的屏幕显示效果。

例3.13 源代码及其 在 phpinfo() 函数中的屏幕显示

php_info_print_table_start();
php_info_print_table_header(2, “First column”, “Second column”);
php_info_print_table_row(2, “Entry in first row”, “Another entry”);
php_info_print_table_row(2, “Just to fill”, “another row here”);
php_info_print_table_end();

执行时信息

你还可以输出一些执行时信息,像当前被执行的文件名、当前正在执行的函数名等等。当前正在执行的函数名可以通过 get_active_function_name() 函数来获取。这个函数没有参数(译注:原文即是如此,事实上是跟后面提到的 zend_get_executed_filename() 函数一样需要提交 TSRMLS_C 宏参数,译注),返回值为函数名的指针。当前被执行的文件名可以由 zend_get_executed_filename() 函数来获得。这个函数需要传入 TSRMLS_C 宏参数来访问执行器全局变量。这个执行器全局变量对每个被 Zend 直接调用的函数都是有效的(因为 TSRMLS_C 是我们前文讨论过的参数宏 INTERNAL_FUNCTION_PARAMETERS 的一部分)。如果你想在其他函数中也访问这个执行器全局变量,那就需要现在那个函数中调用一下宏 TSRMLS_FETCH()

最后你还可以通过 zend_get_executed_lineno() 函数来取得当前正在执行的那一行代码所在源文件中的行数。这个函数同样需要访问执行器全局变量作为其参数。关于这些函数的应用,请参阅“例3.14 输出执行时信息”。

例3.14 输出执行时信息

zend_printf("The name of the current function is %s<br>", get_active_function_name(TSRMLS_C));
zend_printf(”The file currently executed is %s<br>”, zend_get_executed_filename(TSRMLS_C));
zend_printf(”The current line being executed is %i<br>”, zend_get_executed_lineno(TSRMLS_C));

关于扩展内函数到 PHP 脚本的返回值我们前面谈得比较少,这一节我们就来详细说一下。任何函数的返回值都是通过一个名为 return_value 的变量传递的。这个变量同时也是函数中的一个参数。这个参数总是包含有一个事先申请好空间的 zval 容器,因此你可以直接访问其成员并对其进行修改而无需先对 return_value 执行一下 MAKE_STD_ZVAL 宏指令。

为了能够更方便从函数中返回结果,也为了省却直接访问 zval 容器内部结构的麻烦,ZEND 提供了一大套宏命令来完成相关的这些操作。这些宏命令会自动设置好类型和数值。“表3.14 从函数直接返回值的宏”和“表3.15 设置函数返回值的宏”列出了这些宏和对应的说明。

注意:使用“表3.14 从函数直接返回值的宏”会自动携带结果从当前函数返回。而使用“表3.15 设置函数返回值的宏”则只是设置了一下函数返回值,并不会马上返回。

表3.14 从函数直接返回值的宏

说明
RETURN_RESOURCE(resource) 返回一个资源。
RETURN_BOOL(bool) 返回一个布尔值。
RETURN_NULL() 返回一个空值。
RETURN_LONG(long) 返回一个长整数。
RETURN_DOUBLE(double) 返回一个双精度浮点数。
RETURN_STRING(string, duplicate) 返回一个字符串。duplicate 表示这个字符是否使用 estrdup() 进行复制。
RETURN_STRINGL(string, length, duplicate) 返回一个定长的字符串。其余跟 RETURN_STRING 相同。这个宏速度更快而且是二进制安全的。
RETURN_EMPTY_STRING() 返回一个空字符串。
RETURN_FALSE 返回一个布尔值假。
RETURN_TRUE 返回一个布尔值真。

表3.15 设置函数返回值的宏

说明
RETVAL_RESOURCE(resource) 设定返回值为指定的一个资源。
RETVAL_BOOL(bool) 设定返回值为指定的一个布尔值。
RETVAL_NULL 设定返回值为空值
RETVAL_LONG(long) 设定返回值为指定的一个长整数。
RETVAL_DOUBLE(double) 设定返回值为指定的一个双精度浮点数。
RETVAL_STRING(string, duplicate) 设定返回值为指定的一个字符串,duplicate 含义同 RETURN_STRING
RETVAL_STRINGL(string, length, duplicate) 设定返回值为指定的一个定长的字符串。其余跟 RETVAL_STRING 相同。这个宏速度更快而且是二进制安全的。
RETVAL_EMPTY_STRING 设定返回值为空字符串。
RETVAL_FALSE 设定返回值为布尔值假。
RETVAL_TRUE 设定返回值为布尔值真。

如果需要返回的是像数组和对象这样的复杂类型的数据,那就需要先调用 array_init()object_init(),也可以使用相应的 hash 函数直接操作 return_value。由于这些类型主要是由一些杂七杂八的东西构成,所以对它们就没有了相应的宏。

迟早你会遇到把一个 zval 容器的内容赋给另外一个 zval 容器的情况。不过可别想当然,这事说起来容易做起来可有点难度。因为 zval 容器不但包含了类型信息,而且还有对 Zend 内部数据的一些引用。比如,数组以及对象等依据其大小大都或多或少包含了一些哈希表结构。而我们在将一个 zval 赋给另外一个 zval 时,通常都没有复制这些哈希表本身,复制的只是这些哈希表的引用而已。

为了能够正确复制这些复杂类型的数据,我们可以使用“拷贝构造函数(copy constructor)”来完成这项工作。拷贝构造函数在某些为了可以复制复杂类型数据而支持操作符重载的语言中有着代表性的应用。如果你在这种语言中定义了一个对象,那你就可能想为其重载(Overloading)一下“=”操作符,这个操作符通常用于将右值(操作符右边表达式的值)赋给左值(操作符左边表达式的值)。

“重载”就意味着将给予这个操作符另外一种不同的含义,它通常会把这个操作符跟某个函数调用关联起来。当这个操作符作用在一个对象上时,与之关联的函数就将会被调用,同时该操作符的左值和右值也会作为该函数的参数一并传入。这样,这个函数就可以完成“=”操作符想要完成的事情(一般是某些额外数据的复制)。

这些“额外数据的复制”对 PHP 的 zval 容器来说也是很有必要的。对于数组来说,“额外数据的复制”就是指另外再重建和复制那些与该数组有关的哈希表(因为当初我们复制 zval 时复制的仅仅是这些哈希表的指针)。而对字符串来说,“额外数据的复制”就意味着我们必须重新为字符串值去申请空间。如此类推。

Zend Engine 会调用一个名为 zval_copy_ctor()(在以前的 PHP 版本中这个函数叫做 pval_copy_constructor() )的函数来完成这项工作。

下面这个示例为我们展示了这样一个函数:它接收一个复杂类型的参数,在对其进行一定的修改后把它作为结果返回给 PHP:

zval *parameter;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “z”, &parameter) == FAILURE)
  return;
}
// 在这对参数做一定的修改

……
// 返回修改后的容器
*return_value = *parameter;
zval_copy_ctor(return_value);

函数的头一部分没什么可说的,只是一段很平常的接收参数的代码而已。不过在对这个参数进行了某些修改后就变得有趣起来了:先是把 parameter 容器值赋给了(预先定义好的)return_value 容器,然后为了能够真正复制这个容器,我们便调用了拷贝构造函数。这个拷贝构造函数能够直接处理它的参数,处理成功则返回 SUCCESS,否则返回 FAILURE

在这个例子当中如果你忘了调用这个拷贝构造函数,那么 parameterreturn_value 就会分别指向同一个 Zend 内部数据,也就是说返回值 return_value 非法指向了一个数据结构。当你修改了参数 parameter 时这个函数的返回值就可能会受到影响。因此为了创建一个独立的拷贝,我们必须调用这个函数。

在 Zend API 中还有一个与拷贝构造函数相对应的拷贝析构函数:zval_dtor(),它做的工作正好与拷贝构造函数相反。

当 PHP 脚本与扩展互相交换数据时,我们还需要做一件很重要的事情,那就是创建变量。这一小节将会展示如何处理那些 PHP 脚本所支持的变量类型。

概述

要创建一个能够被 PHP 脚本所访问的“外部变量”,我们只需先创建一个 zval 容器,然后对这个 zval 结构进行必要的填充,最后再把它引入到 Zend 的内部符号表中就可以了。而且几乎所有变量的创建基本上都是这几个步骤:

zval *new_variable;
/* 申请并初始化一个新的的 zval 容器 */
MAKE_STD_ZVAL(new_variable);

/* 设置变量的类型和内容,见下 */

/* 将名为 “new_variable_name” 变量引入符号表 */
ZEND_SET_SYMBOL(EG(active_symbol_table), “new_variable_name”, new_variable);

/* 现在就可以在脚本中用 $new_variable_name 来访问这个变量了 */

宏 MAKE_STD_ZVAL 通过 ALLOC_ZVAL 来申请一个新的 zval 容器的内存空间并调用 INIT_ZVAL(查看 PHP4/5 的源代码可知此处为原文笔误,实际上应为 INIT_PZVAL,下同。译注)将其初始化。在当前的 Zend Engine 中,INIT_ZVAL 所负责的初始化工作除了将 zval 容器的引用计数(refcount)置为 1 之外,还会把引用标识也清除(即把 is_ref  也置为 0)。而且在以后的 Zend Engine 中还可能会继续扩展这个 INIT_ZVAL 宏操作,因此我们推荐您使用 MAKE_STD_ZVAL 而非简单使用一个 ALLOC_ZVAL 来完成一个变量的创建工作。当然,如果您是想优化一下速度(或者是不想明确地初始化这个 zval 容器),那还是可以只用 ALLOC_ZVAL 来搞定的。不过我们并不推荐这么做,因为这将不能保证数据的完整性。

ZEND_SET_SYMBOL 宏负责将我们新建的变量引入 Zend 内部的符号表。这个宏会首先检查一下这个变量是否已经存在于符号表中,如果已经存在则将其转换为一个引用变量(同时会自动销毁原有的 zval 容器)。事实上这个方法经常用在某些速度要求并不苛刻但希望能少用一些内存的情况下。

您可能注意到了 ZEND_SET_SYMBOL 是通过宏 EG 来访问 Zend 执行器(executor)的全局结构的。特别的,如果你使用的是 EG(active_symbol_table),那你就可以访问到当前的活动符号表,从而可以处理一些全局或局部变量。其中局部变量可能会依不同的函数而有所不同。.

当然,要是你很在意程序的运行速度并且不在乎那一点点内存的话,那你可以跳过对相同名字变量存在性的检查而直接使用 zend_hash_update() 函数强行将这个名字的变量插入符号表。

zval *new_variable;

/* 申请并初始化一个新的的 zval 容器 */
MAKE_STD_ZVAL(new_variable);

/* 设置变量的类型和内容,见下 */

 
/* 将名为 “new_variable_name” 变量引入符号表 */
zend_hash_update(
   EG(active_symbol_table),
   ”new_variable_name”,
   strlen(”new_variable_name”) + 1,
   &new_variable,
   sizeof(zval *),
   NULL
);

实际上这段代码也是很多扩展使用的标准方法。

上面这段代码所产生的变量是局部变量,作用范围跟调用函数的上下文相关。如果你想创建一个全局变量那也很简单,方法还是老方法,只需换个符号表就可以了。

zval *new_variable;


/* 申请并初始化一个新的的 zval 容器 */
MAKE_STD_ZVAL(new_variable);

/* 设置变量的类型和内容,见下 */

/* 将名为 “new_variable_name” 变量引入全局符号表 */


ZEND_SET_SYMBOL(&EG(symbol_table), “new_variable_name”, new_variable);

注意,现在宏 ZEND_SET_SYMBOL 使用的符号表是全局符号表 EG(symbol_table)。另外,active_symbol_table 是一个指针,而 symbol_table 却不是。这就是我们为什么分别使用 EG(active_symbol_table)&EG(symbol_table) 的原因 - ZEND_SET_SYMBOL 需要一个指针作为其参数。

当然,你同样也可以强行更新这个符号表:

zval *new_variable;

/* 申请并初始化一个新的的 zval 容器 */
MAKE_STD_ZVAL(new_variable);

/* 设置变量的类型和内容,见下 */

 

/* 将名为 “new_variable_name” 变量引入全局符号表 */
zend_hash_update(
   &EG(symbol_table),
   ”new_variable_name”,
   strlen(”new_variable_name”) + 1,
   &new_variable,
   sizeof(zval *),
   NULL
);

例 3.9 “创建不同作用域的变量”向我们展示了创建一个局部变量(local_variable)和一个全局变量(global_variable)的过程。.

注意:你可能会发现在 PHP 函数里似乎还不能直接访问这个全局变量(global_variable),因为你在使用前还必须使用 global $global_variable; 声明一下。


例3.9 创建不同作用域的变量

ZEND_FUNCTION(variable_creation)
{
   zval *new_var1, *new_var2;
   MAKE_STD_ZVAL(new_var1);
   MAKE_STD_ZVAL(new_var2);
   ZVAL_LONG(new_var1, 10);
   ZVAL_LONG(new_var2, 5);
   ZEND_SET_SYMBOL(EG(active_symbol_table), “local_variable”, new_var1);
   ZEND_SET_SYMBOL(&EG(symbol_table), “global_variable”, new_var2);
   RETURN_NULL();
}


长整型(整数)

现在让我们以长整型变量起点,了解一下如何为一个变量赋值。PHP 中的整数全部是长整型,其值的存储方法也是非常简单的。看一下我们前面讨论过的 zval.value 容器的结构你就会明白,所有的长整型数据都是直接保存在这个联合中的 lval 字段,相应的数据类型(type 字段)为 IS_LONG(见 例3.10 “长整型变量的创建”)。


例3.10 长整型变量的创建

zval *new_long;
MAKE_STD_ZVAL(new_long);
new_long->type = IS_LONG;
new_long->value.lval = 10;

或者你也可以直接使用 ZVAL_LONG 宏:

zval *new_long;
MAKE_STD_ZVAL(new_long);
ZVAL_LONG(new_long, 10);

双精度型(浮点数)

PHP 中的浮点数都是双精度型,存储方法和整型差不多,也很简单。它的值是直接放在联合中的 dval 字段,对应数据类型为 IS_DOUBLE

zval *new_double;
MAKE_STD_ZVAL(new_double);
new_double->type = IS_DOUBLE;
new_double->value.dval = 3.45;

同样你也可以直接使用宏 ZVAL_DOUBLE

zval *new_double;
MAKE_STD_ZVAL(new_double);
ZVAL_DOUBLE(new_double, 3.45);

字符串

字符串的存储可能会稍费点事。字符串的值是保存在 zval.value 容器中的 str 结构里面,相应的数据类型为 IS_STRING。不过需要注意的是,前面我们已经提到过,所有与 Zend 内部数据结构相关的字符串都必须使用 Zend 自己的内存管理函数来申请空间。这样一来,就不能使用那些静态字符串(因为这种字符串的内存空间是编译器预先分配的)或通过标准函数(比如 malloc() 等函数)来申请空间的字符串。

zval *new_string;
char *string_contents = “This is a new string variable”;
MAKE_STD_ZVAL(new_string);
new_string->type = IS_STRING;
new_string->value.str.len = strlen(string_contents);
new_string->value.str.val = estrdup(string_contents);

请注意,在这我们使用了 estrdup() 函数。当然我们仍可直接使用一个预定义宏 ZVAL_STRING 来完成这项工作:

zval *new_string;
char *string_contents = “This is a new string variable”;
MAKE_STD_ZVAL(new_string);
ZVAL_STRING(new_string, string_contents, 1);

ZVAL_STRING 宏的第三个参数指明了该字符串是否需要被复制(使用 estrdup() 函数)。值为 1 将导致该字符串被复制,为 0 时则仅仅是简单地将其指向该变量的值容器(即字符串地址,译注)。这项特性将会在你仅仅需要创建一个变量并将其指向一个已经由 Zend 内部数据内存时变得很有用。

如果你想在某一位置截取该字符串或已经知道了这个字符串的长度,那么可以使用宏 ZVAL_STRINGL(zval, string, length, duplicate) 来完成这项工作。这个函数会额外需要一个表明该字符串长度地参数。这个宏不但速度上要比 ZVAL_STRING 快,而且还是二进制安全的。

如果想创建一个空字符串,那么将其长度置 0 并且把 empty_string 作为字符串的内容即可:

new_string->type = IS_STRING;
new_string->value.str.len = 0;
new_string->value.str.val = empty_string;

当然,我们也专门为您准备了一个相应的宏 ZVAL_EMPTY_STRING 来搞定这个步骤:

MAKE_STD_ZVAL(new_string);
ZVAL_EMPTY_STRING(new_string);

布尔类型

布尔类型变量的创建跟长整型差不多,只是数据类型为 IS_BOOL,并且字段 lval 所允许的值只能为 0 和 1:

zval *new_bool;
MAKE_STD_ZVAL(new_bool);
new_bool->type = IS_BOOL;
new_bool->value.lval = 1;

也可以使用宏 ZVAL_BOOL (需要另外指定一个值)来完成这件事情,或者干脆直接使用ZVAL_TRUEZVAL_FALSE 直接将其值设定为 TRUEFALSE

数组

数组在 Zend 内部是用哈希表(HashTable)来存储的,这个哈希表可以使用一系列的 zend_hash_*() 函数来访问。因此我们在创建一个数组时必须先创建一个哈希表,然后再将其保存在 zval.value 容器的 ht 字段中。

不过针对数组的创建我们现在另有一套非常方便 API 可供使用。为了创建一个数组,我们可先调用一下 array_init() 函数:.

zval *new_array;
MAKE_STD_ZVAL(new_array);
array_init(new_array);

array_init() 函数总是返回 SUCCESS

要给数组增加一个元素,根据实际需要,我们有 N 个函数可供调用。“表3.8 用于关联数组的 API”、“表3.9 用于索引数组的 API 第一部分”和“表3.10 用于索引数组的 API 第二部分”有这些函数的说明。所有这些函数在调用成功时返回 SUCCESS,在调用失败时返回 FAILURE。


表3.8 用于关联数组的 API

函数 说明
add_assoc_long(zval *array, char *key, long n); 添加一个长整型元素。
add_assoc_unset(zval *array, char *key); 添加一个 unset 元素。
add_assoc_bool(zval *array, char *key, int b); 添加一个布尔值。
add_assoc_resource(zval *array, char *key, int r); 添加一个资源。
add_assoc_double(zval *array, char *key, double d); 添加一个浮点值。
add_assoc_string(zval *array, char *key, char *str, int duplicate); 添加一个字符串。duplicate 用于表明这个字符串是否要被复制到 Zend 的内部内存。
add_assoc_stringl(zval *array, char *key, char *str, uint length, int duplicate); 添加一个指定长度的字符串。其余跟add_assoc_string () 相同。
add_assoc_zval(zval *array, char *key, zval *value); 添加一个 zval 结构。 这在添加另外一个数组、对象或流等数据时会很有用。


表3.9 用于索引数组的 API 第一部分

函数 说明
add_index_long(zval *array, uint idx, long n); 添加一个长整型元素。
add_index_unset(zval *array, uint idx); 添加一个 unset 元素。
add_index_bool(zval *array, uint idx, int b); 添加一个布尔值。
add_index_resource(zval *array, uint idx, int r); 添加一个资源。
add_index_double(zval *array, uint idx, double d); 添加一个浮点值。
add_index_string(zval *array, uint idx, char *str, int duplicate); 添加一个字符串。duplicate 用于表明这个字符串是否要被复制到 Zend 的内部内存。
add_index_stringl(zval *array, uint idx, char *str, uint length, int duplicate); 添加一个指定长度的字符串。其余跟add_index_string () 相同。
add_index_zval(zval *array, uint idx, zval *value); 添加一个 zval 结构。 这在添加另外一个数组、对象或流等数据时会很有用。


表3.10 用于索引数组的 API 第二部分

函数 说明
add_next_index_long(zval *array, long n); 添加一个长整型元素。
add_next_index_unset(zval *array); 添加一个 unset 元素。
add_next_index_bool(zval *array, int b); 添加一个布尔值。
add_next_index_resource(zval *array, int r); 添加一个资源。
add_next_index_double(zval *array, double d); 添加一个浮点值。
add_next_index_string(zval *array, char *str, int duplicate); 添加一个字符串。duplicate 用于表明这个字符串是否要被复制到 Zend 的内部内存。
add_next_index_stringl(zval *array, char *str, uint length, int duplicate); 添加一个指定长度的字符串。其余跟add_next_index_string () 相同。
add_next_index_zval(zval *array, zval *value); 添加一个 zval 结构。 这在添加另外一个数组、对象或流等数据时会很有用。

所有这些函数都是对 Zend 内部 hash API 的一种友好抽象。因此,若你愿意,你大可直接使用那些 hash API 进行操作。比方说,假如你已经有了一个 zval 容器并想把它插入到一个数组,那么你就可以直接使用 zend_hash_update() 来把它添加到一个关联数组(例3.11 给关联数组添加一个元素)或索引数组(例3.12 给索引数组添加一个元素)。


例3.11 给关联数组添加一个元素

zval *new_array, *new_element;
char *key = “element_key”;
MAKE_STD_ZVAL(new_array);
MAKE_STD_ZVAL(new_element);
array_init(new_array);
ZVAL_LONG(new_element, 10);
if(zend_hash_update(new_array->value.ht, key, strlen(key) + 1, (void *)&new_element, sizeof(zval *), NULL) == FAILURE)
{
   // do error handling here
}


例3.12 给索引数组添加一个元素

zval *new_array, *new_element;
int key = 2;
MAKE_STD_ZVAL(new_array);
MAKE_STD_ZVAL(new_element);
array_init(new_array);
ZVAL_LONG(new_element, 10);
if(zend_hash_index_update(new_array->value.ht, key, (void *)&new_element, sizeof(zval *), NULL) == FAILURE)
{
   // do error handling here
}

如果还想模拟下 add_next_index_*() ,那可以这么做:  

zend_hash_next_index_insert(ht, zval **new_element, sizeof(zval *), NULL)

注意:如果要从函数里面返回一个数组,那就必须首先对预定义变量 return_value (return_value 是我们导出函数中的一个预定义参数,用来存储返回值)使用一下 array_init() 函数。不过倒不必对其使用 MAKE_STD_ZVAL 。

提示:为了避免一遍又一遍地书写 new_array->value.ht,我们可以用 HASH_OF(new_array) 来代替。而且出于兼容性和风格上的考虑,我们也推荐您这么做。

对象

既然对象可以被转换成数组(反之亦然),那么你可能已经猜到了两者应该具有很多相似之处。实际上,对象就是使用类似的函数进行操作的,所不同的是创建它们时所用的 API。

我们可以调用 object_init() 函数来初始化一个对象:

zval *new_object;
MAKE_STD_ZVAL(new_object);
if(object_init(new_object) != SUCCESS)
{
   // do error handling here
}

可以使用“表3.11 用于创建对象的 API”来给对象添加一些成员。


表3.11 用于创建对象的 API

函数 说明
add_property_long(zval *object, char *key, long l); 添加一个长整型类型的属性值。
add_property_unset(zval *object, char *key); 添加一个 unset 类型的属性值。
add_property_bool(zval *object, char *key, int b); 添加一个布尔类型的属性值。
add_property_resource(zval *object, char *key, long r); 添加一个资源类型的属性值。
add_property_double(zval *object, char *key, double d); 添加一个浮点类型的属性值。
add_property_string(zval *object, char *key, char *str, int duplicate); 添加一个字符串类型的属性值。
add_property_stringl(zval *object, char *key, char *str, uint length, int duplicate); 添加一个指定长度的字符串类型的属性值,速度要比 add_property_string() 函数快,而且是二进制安全的。
add_property_zval(zval *obect, char *key, zval *container); 添加一个 zval 结构的属性值。 这在添加另外一个数组、对象等数据时会很有用。
资源

资源是 PHP 中一种比较特殊的数据类型。“资源”这个词其实并不特指某些特殊类型的数据,事实上,它指的是一种可以维护任何类型数据信息方法的抽象。所有的资源均保存在一个 Zend 内部的资源列表当中。列表中的每份资源都有一个指向可以表明其种类的类型定义的指针。Zend 在内部统一管理所有对资源的引用。直接访问一个资源是不大可能的,你只能通过提供的 API 来对其进行操作。某个资源一旦失去引用,那就会触发调用相应的析构函数。

举例来说,数据库连接和文件描述符就是一种资源。MySQL 模块中就有其“标准”实现。当然其他模块(比如 Oracle 模块)也都用到了资源。

注意:

实际上,一个资源可以指向函数中任何一种你所感兴趣的数据(比如指向一个结构等等)。并且用户也只能通过某个资源变量来将资源信息传递给相应的函数。

要想创建一个资源你必须先注册一个这个资源的析构函数。这是因为Zend 需要了解当你把某些数据存到一个资源里后,如果不再需要这份资源时该如何将其释放。这个析构函数会在释放资源(无论是手工释放还是自动释放)时被 Zend 依次调用。析构函数注册后,Zend 会返回一个此种资源类型句柄。这个句柄会在以后任何访问此种类型的资源的时候被用到,而且这个句柄绝大部分时间都保存在扩展的全局变量里面。这里你不需要担心线程安全方面的问题,因为你只是需要在模块初始化注册一次就行了。

下面是这个用于注册资源析构函数的 Zend 函数定义:

ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number);

你或许已经注意到了,在该函数中我们需要提供两种不同的资源析构函数:一种是普通资源的析构函数句柄,一种是持久化资源的析构函数句柄。持久化资源一般用于诸如数据库连接等这类情况。在注册资源时,这两个析构函数至少得提供一个,另外一个析构函数可简单地设为 NULL。

zend_register_list_destructors_ex() 接受以下几个参数:

ld 普通资源的析构函数。
ld 持久化资源的析构函数。
type_name 为你的资源指定一个名称。在 PHP 内部为某个资源类型起个名字这是个好习惯(当然名字不能重复)。用户调用 var_dump($resource) 时就可取得该资源的名称。
module_number 这个参数在你模块的 PHP_MINIT_FUNCTION 函数中会自动定义,因此你大可将其忽略。

返回值是表示该资源类型的具有唯一性的整数标识符,即资源类型句柄

资源(不论是不是持久化资源)的析构函数都必须具有以下的函数原型:

void resource_destruction_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC);

参数 rsrc 指向一个 zend_rsrc_list_entry 结构:

typedef struct _zend_rsrc_list_entry {
   void *ptr;
   int type;
   int refcount;
} zend_rsrc_list_entry;

成员 void *ptr 才真正指向你的资源。

现在我们就知道该怎么开始了。我们先定义一个将要注册到 Zend 内部的资源类型 my_resource,这个类型的结构很简单,只有两个整数成员:

typedef struct {
   int resource_link;
   int resource_type;
} my_resource;

接着我们再定义一下这种资源的析构函数。这个析构函数大致上是以下这个样子:

void my_destruction_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC) {
   // 先将无类型指针转换为我们的资源类型指针
   my_resource *my_rsrc = (my_resource *) rsrc->ptr;


   // 现在我们就可以随意处理这些资源了:像关闭文件、释放内存等等。
   // 当然也不要忘了释放资源本身所占用的内存!
   do_whatever_needs_to_be_done_with_the_resource(my_rsrc);
}

注意:

有一个很重要的事情必须要提一下:如果你的资源是一个比较复杂的结构,比如包含有你在运行时所申请内存的指针等,那你就必须在释放资源本身前释放它们!

OK。现在我们定义了

  1. 我们的资源是什么样子;

  2. 我们资源的析构函数是什么样子。

那么,我们还需要做哪些工作呢?我们还需要:

  1. 创建一个在整个扩展范围内有效的全局变量用于保存资源类型句柄,这样就可以在每个需要它的函数中都能访问到它;

  2. 给我们的资源类型定义一个名称;

  3. 完成前面定义的资源析构函数;

  4. 最后注册这个析构函数。

   // 在你扩展的某个地方定义一个表示资源类型的变量
   static int le_myresource;

   // 给我们的资源起个名字是个很不错的习惯
   #define le_myresource_name  ”My type of resource”

   [...]

   // 现在完成我们资源的析构函数
   void my_destruction_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC) {
       my_resource *my_rsrc = (my_resource *) rsrc->ptr;
       do_whatever_needs_to_be_done_with_the_resource(my_rsrc);
   }

   [...]

   PHP_MINIT_FUNCTION(my_extension) {
       // 注意 ‘module_number’ 已经在 PHP_MINIT_FUNCTION() 函数中被定义过了
       le_myresource = zend_register_list_destructors_ex(my_destruction_handler, NULL, le_myresource_name, module_number);

       // 然后你可以在这里注册一些附加资源、初始化全局变量、常量等等。
   }

注册完这种资源的析构函数后,要真正注册一个资源(实例),我们可以使用 zend_register_resource() 函数或使用 ZEND_REGISTER_RESOURE() 宏。这两个的定义可以在 zend_list.h 中找到。尽管两者的参数定义都是一一对应的,但使用宏通常可以得到更好的前向兼容性:

int ZEND_REGISTER_RESOURCE(zval *rsrc_result, void *rsrc_pointer, int rsrc_type);

rsrc_result 这是一个初始化过 zval * 容器。
rsrc_pointer 指向所保存的资源。
rsrc_type 这个参数就是你在注册函数析构函数时返回的资源类型句柄。对上面的代码来说就是le_myresource le_myresource。

返回值就是表示这个资源(实例)的具有唯一性的整数。

那么在我们注册这个资源(实例)时究竟发生了什么事呢?函数会从 Zend 内部某个列表取得一个空闲空间,然后将资源指针及类型保存到这个空间。最后这个空闲空间的索引被简单地保存在给定的 zval * 容器里面:

rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type);
   if (rsrc_result) {
       rsrc_result->value.lval = rsrc_id;
       rsrc_result->type = IS_RESOURCE;
   }
   return rsrc_id;

返回值 rsrc_id 就唯一性地标识了我们新注册得到的那个资源。你可以使用宏RETURN_RESOURE 来将其返回给用户:

RETURN_RESOURCE(rsrc_id)

注意:

如果你想立刻把这个资源返回给用户,那你就应该把 return_value 作为那个 zval * 容器。这也是我们推荐的一种编程实践。

Zend 引擎从现在就会开始跟踪所有对这个资源的引用。一旦对这个资源的引用全都不存在了,那么你在前面为这个资源所注册的析构函数就会被调用。这样做的好处就是你不用担心会在你的模块里面引入内存泄漏-你只需要把你调用脚本中所有需要分配的内存都注册成资源即可。这样一来,一旦脚本认为不再需要它们的时候,Zend 就会找到它们然后再通知你(这就是 callback,译注)。

现在用户已经通过在某处传入到你函数的参数拿到了他的资源。zval * 容器中的 value.lval 包含了你资源的标识符,然后他就可以宏 ZEND_FETCH_RESOURCE 来获取资源了:

ZEND_FETCH_RESOURCE(rsrc, rsrc_type, rsrc_id, default_rsrc_id, resource_type_name, resource_type)

rsrc 这个指针将指向你前面已经声明过的资源。
rsrc_type 这个参数用以表明你你想要把前面参数的那个指针转换为何种类型。比如 myresource * 等等。
rsrc_id 这个是用户传进你函数的那个 zval *container 的地址。 假如给出的是 zval *z_resource ,那么此处就应该是 &z_resource
default_rsrc_id 这个参数表明假如没有取到资源时默认指定的资源标识符。通常为 -1。
resource_type_name 所请求的资源类型资源类型名称。当不能找到资源时,就用这个字符串去填充系统由于维护而抛出的错误信息。
resource_type 这个可以取回在注册资源析构函数时返回的资源类型。在本例就是 le_myresource

这个宏没有返回值。这对开发人员可能会方便了点。不过还是要注意添加 TSRM 参数和确认一下是否取回了资源。如果在接收资源时出现了问题,那它就会抛出一个警告信息并且会立刻从当前函数返回,其返回值为 NULL。

如果想从列表强行删除一个资源,可以使用 zend_list_delete() 函数。当然也可以强行增加引用计数,如果你知道你正在创建一个指向已分配内存资源的引用(比如说你可能想重用一个默认的数据库连接)。对于这种情况你可以使用函数 zend_list_addref() 。想要查找一个已分配内存的资源,请使用 zend_list_find() 函数。关于这些操作的完整 API 请参见 zend_list.h

自动创建全局变量的宏

作为我们早期所谈论的一些宏的补充,还有一些宏可以让我们很方便的创建全局变量。了解了它们,我们在引入一些全局标识时就会感觉很爽,不过这个习惯可能会不太好。在“表3.12 创建全局变量的宏”中描述了完成这些任务所用到的正确的宏。它们不需要申请任何 zval 容器,你只需简单地提供一个变量名和其值即可。


表3.12 创建全局变量的宏

说明
SET_VAR_STRING(name, value) 新建一个字符串变量。
SET_VAR_STRINGL(name, value, length) 新建一个指定长度的字符串变量。这个宏要比 SET_VAR_STRING 快而且还是二进制安全的。
SET_VAR_LONG(name, value) 新建一个长整型变量。
SET_VAR_DOUBLE(name, value) 新建一个双精度变量。
创建常量

Zend 支持创建真正的常量。访问常量时不需要 $ 前缀,而且常量是全局有效的。比如 TRUEFALSE 这两个常量。

要想创建一个常量,你可以使用“表3.13 创建常量的宏”中所列举的宏来完成这项工作。所有的宏在创建常量时都必须指定一个名称和值。

你还可以为常量指定一个特别的标识:

  • CONST_CS - 这个常量的名称是大小写敏感的;

  • CONST_PERSISTENT - 这个常量是持久化的。换句话说,当携带这个常量的进程关闭时这个常量在剩下的请求中还依然有效,并不会被“遗忘”。

可以使用二进制的“或(OR)”操作来使用其中的一个或两个标识:

    // 注册一个长整型常量
    REGISTER_LONG_CONSTANT(”NEW_MEANINGFUL_CONSTANT”, 324, CONST_CS |
    CONST_PERSISTENT);

我们提供有两种不同类型的宏,分别是 REGISTER_*_CONSTANTREGISTER_MAIN_*_CONSTANT。第一种类型在创建常量时只会绑定到当前模块。一旦注册这个模块的常量从内存中卸载,那么这个常量也就会随即消逝。第二种类型创建的变量将会独立于该模块,始终保存在符号表中。


表3.13 创建常量的宏
说明
REGISTER_LONG_CONSTANT(name, value, flags) REGISTER_MAIN_LONG_CONSTANT(name, value, flags) 新建一个长整型常量。
REGISTER_DOUBLE_CONSTANT(name, value, flags) REGISTER_MAIN_DOUBLE_CONSTANT(name, value, flags) 新建一个双精度型常量。
REGISTER_STRING_CONSTANT(name, value, flags) REGISTER_MAIN_STRING_CONSTANT(name, value, flags) 新建一个字符串常量。给定的字符串的空间必须在Zend 内部内存。
REGISTER_STRINGL_CONSTANT(name, value, length, flags) REGISTER_MAIN_STRINGL_CONSTANT(name, value, length, flags) 新建一个指定长度的字符串常量。同样,这个给定的字符串的空间也必须在Zend 内部内存。

算起来这个站点都快有三个月打不开了。不过还好,在一些朋友的帮助下现在似乎是没什么大问题了。

尽管在这个 Blog 上有三个月的空白,但在实际生活中却并非如此。相反,这三个月经过的事情貌似比上一年加起来还多。当然,过去的已经过去,新的生活才刚刚开始~

本年度剩余时间的打算:

    1、完善一下现有的 PHP 反编译器,完成难度 4;
    2、写一款较为完善的 PHP 加密工具,完成难度 5;
    3、使用 Delphi 写一款 PHP 的 IDE,完成难度 7;
    4、找一份合适的工作,完成难度 8;
    5、找一个合适的MM,完成难度 10。:(

« Previous PageNext Page »