小见识大学问的注册 PHP 函数
C 说话的 PHP 函数构造和 API
好的。下面是一个 PHP 函数。你可以使用它,并用 PHP 说话声明它(会员区):
function fahrenheit_to_celsius($fahrenheit) { return 5/9 * ($fahrenheit - 32); }
这是一个简便的函数,以便你可以懂得它。这是用 C 编程时的模样:
PHP_FUNCTION(fahrenheit_to_celsius) { /* code to go here */ }
宏展开后,将得到:
void zif_fahrenheit_to_celsius(zend_execute_data *execute_data, zval *return_value) { /* code to go here */ }
歇息一下,思考一下主要差别。
第一惊奇的是,在 C 中,该函数不会返回任何东西。那是一个 void
声明的函数,你不成以在这里返回任何东西。但是我们留意到我们接收了一个 zval *
类型的return_value
参数,看起来很不错。用 C 编写 PHP 函数时,你将得到一个指向 zval 的返回值 ,但愿你们能玩一玩。这有更多关于 zval 的资源.
留意
在 C 扩展中编写 PHP 函数时,你接收作为参数的返回值,并且你不会从 C 函数返回任何东西。
好的,第一点说明了。第二点你大概已经猜到了:PHP 函数的参数在哪里?$fahreinheit
在哪里?很难说明完全,事实上,这很难。
但是我们不需要在这里理解细节。让我们说明下关键的概念:
- 参数已经通过引擎推入堆栈中。它们都在内存的某个地方受着堆放。
- 假如你的函数被调取,这意味着没有堵塞错误,因此你可以阅读参数堆栈,并读取运转时传递的参数。不仅是你声明的那些,还包罗那些在调取函数时传递给函数的。引擎会为你处置一切。
- 为了读取参数,你需要一个函数或者宏,并且需要知道有多少参数已经推入堆栈中,以便知道什么时候应当休止读取它们。
- 一切都依照你接收的作为参数的
zend_execute_data *execute_data
。但是此刻我们不具体说明。
解析参数:zend_parse_parameters()
要读取参数,欢迎使用 zend_parse_parameters()
API (称为 ‘zpp’).
留意
当在 C 扩展中编写 PHP 函数时,多亏了
zend_parse_parameters()
函数和它的伴侣,你接收到 PHP 函数的参数。
zend_parse_parameters()
是一个函数,它将为你到 Zend 引擎的堆栈中读取参数。你要告诉它要读取多少个参数,乃至想要它为你供给哪品种型。该函数将按照 PHP 类型转换规则(假如需要,并且有大概的话)将参数转换为你要的类型。假如你需要一个整型,但给了一个浮点型,假如没有严厉的类型提醒规则被堵塞,则引擎会将浮点型转换为整型,然后给你。
让我们来看看这个函数:
PHP_FUNCTION(fahrenheit_to_celsius) { double f; if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &f) == FAILURE) { return; } /* continue */ }
我们但愿在 f 变量上得到一个 double 类型。然后我们调取zend_parse_parameters()
。
第一个参数是运转时已给定的参数数目。ZEND_NUM_ARGS()
是一个宏,它会告诉我们,然后我们用它去告知 zpp() 需要读取多少个参数。
然后我们传递一个const char *
类型的 “d” 字符串。在这里,要求你为每一个接收的参数写一个字母,除了一些未在这里讲述的非凡状况。一个简便的 “d” 表示 “假如需要的话,我想要第一个接收的参数转换为 float (double)”。
然后,在该字符串之后传递 C 真正需要的参数,以知足第二个参数。一个 “d” 表示 “一个 double”,然后你此刻传递 double 的 地址,引擎将会填充其值。
留意
你总是将一个指针传递给要填充的数据。
你可以在 PHP 源代码的 README.PARAMETER_PARSING_API文件中寻到关于 zpp() 的字符串格局的最新帮忙。细心阅读,由于这是你大概搞错并造成程序崩溃的一步。始终检查你的参数,始终按照你供给的格局字符串传递雷同数目的参数变量,乃至你要求的类型雷同。要符合逻辑。
一样留意一下参数解析的正常历程。zend_parse_parameters()
函数在成功时应返回 SUCCESS
或者在失败时应返回FAILURE
。失败大概表示你没有使用ZEND_NUM_ARGS()
值,而是手动供给一个值(坏主意)。或者在参数解析时做错了什么。假如是这样,那么是时候 return 了,终止当前函数(你应当从 C 函数中返回 void
,所以只要 return
)。
到当前为止,我们接收了一个 double。让我们施行数学运算并返回结果:
static double php_fahrenheit_to_celsius(double f) { return ((double)5/9) * (double)(f - 32); } PHP_FUNCTION(fahrenheit_to_celsius) { double f; if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &f) == FAILURE) { return; } RETURN_DOUBLE(php_fahrenheit_to_celsius(f)); }
如你所知的zval 的工作道理,返回值对你来说应当很容易。你必需填写 return_value
。
一些 RETURN_***()
宏乃至一些RETVAL_***()
宏都是专门用来这么做的。这两个仅设定return_value
zval 的类型和值,但是RETURN_***()
宏后面会跟着一个从当前函数返回的 Creturn
。
或者,API 供给了一系列去处置和解析参数的宏。假如你对 python 样式说明符困惑的话,那么它更具有可读性。
你需要使用以下宏来开端和完毕函数参数解析:
ZEND_PARSE_PARAMETERS_START(min_argument_count, max_argument_count) /* 需要两个参数 */ /* 这里我们将使用参数列表 */ ZEND_PARSE_PARAMETERS_END();
可用的参数宏可以列出如下:
Z_PARAM_ARRAY() /* old "a" */ Z_PARAM_ARRAY_OR_OBJECT() /* old "A" */ Z_PARAM_BOOL() /* old "b" */ Z_PARAM_CLASS() /* old "C" */ Z_PARAM_DOUBLE() /* old "d" */ Z_PARAM_FUNC() /* old "f" */ Z_PARAM_ARRAY_HT() /* old "h" */ Z_PARAM_ARRAY_OR_OBJECT_HT() /* old "H" */ Z_PARAM_LONG() /* old "l" */ Z_PARAM_STRICT_LONG() /* old "L" */ Z_PARAM_OBJECT() /* old "o" */ Z_PARAM_OBJECT_OF_CLASS() /* old "O" */ Z_PARAM_PATH() /* old "p" */ Z_PARAM_PATH_STR() /* old "P" */ Z_PARAM_RESOURCE() /* old "r" */ Z_PARAM_STRING() /* old "s" */ Z_PARAM_STR() /* old "S" */ Z_PARAM_ZVAL() /* old "z" */ Z_PARAM_VARIADIC() /* old "+" and "*" */
为了增加一个参数作为可选参数,我们使用以下宏:
Z_PARAM_OPTIONAL /* old "|" */
这是基于宏的参数解析样式的示例:
PHP_FUNCTION(fahrenheit_to_celsius) { double f; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_DOUBLE(f); ZEND_PARSE_PARAMETERS_END(); RETURN_DOUBLE(php_fahrenheit_to_celsius(f)); }
增加测试
假如你已阅读有关测试的章节(看使用 .phpt 文件测试),此刻你应当编写一个简便的例子:
--TEST-- Test fahrenheit_to_celsius --SKIPIF-- <?php if (!extension_loaded("pib")) print "skip"; ?> --FILE-- <?php printf("%.2f", fahrenheit_to_celsius(70)); ?> --EXPECTF-- 21.11
并启动make test
玩转常量
让我们来看一个高级的例子。我们来增加相反的函数:celsius_to_fahrenheit($celsius)
:
ZEND_BEGIN_ARG_INFO_EX(arginfo_celsius_to_fahrenheit, 0, 0, 1) ZEND_ARG_INFO(0, celsius) ZEND_END_ARG_INFO(); static double php_celsius_to_fahrenheit(double c) { return (((double)9/5) * c) + 32 ; } PHP_FUNCTION(celsius_to_fahrenheit) { double c; if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &c) == FAILURE) { return; } RETURN_DOUBLE(php_celsius_to_fahrenheit(c)); } static const zend_function_entry pib_functions[] = { PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius) /* Done above */ PHP_FE(celsius_to_fahrenheit,arginfo_celsius_to_fahrenheit) /* just added */ PHP_FE_END };
此刻是一个更复杂的用例,在将它作为 C 扩展实现此前,在 PHP 中展现它:
const TEMP_CONVERTER_TO_CELSIUS = 1; const TEMP_CONVERTER_TO_FAHREINHEIT = 2; function temperature_converter($temp, $type = TEMP_CONVERTER_TO_CELSIUS) { switch ($type) { case TEMP_CONVERTER_TO_CELSIUS: return sprintf("%.2f degrees fahrenheit gives %.2f degrees celsius", $temp, fahrenheit_to_celsius($temp)); case TEMP_CONVERTER_TO_FAHREINHEIT: return sprintf("%.2f degrees celsius gives %.2f degrees fahrenheit, $temp, celsius_to_fahrenheit($temp)); default: trigger_error("Invalid mode provided, accepted values are 1 or 2", E_USER_WARNING); break; } }
这个例子有助于我们介绍常量。
常量在扩展中很容易治理,就像它们在会员区一样。常量平常是耐久性的,意味着它们应当在恳求之间保持其值不变。假如你知道 PHP 的生命周期,则应当猜到 MINIT()
是向引擎注册常量的准确阶段。
在内部,这有个常量,一个zend_constant
构造:
typedef struct _zend_constant { zval value; zend_string *name; int flags; int module_number; } zend_constant;
真的是一个简便的构造(假如你深入理解常量是怎样治理到引擎中,那大概会是一场噩梦)。你声明了name
,value
,一些flags
(不是许多),并且module_number
主动设定为你的扩展编号(不消留意它)。
要注册常量,一样的,这一点都不难,一堆宏可以帮你完成:
#define TEMP_CONVERTER_TO_FAHRENHEIT 2 #define TEMP_CONVERTER_TO_CELSIUS 1 PHP_MINIT_FUNCTION(pib) { REGISTER_LONG_CONSTANT("TEMP_CONVERTER_TO_CELSIUS", TEMP_CONVERTER_TO_CELSIUS, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("TEMP_CONVERTER_TO_FAHRENHEIT", TEMP_CONVERTER_TO_FAHRENHEIT, CONST_CS|CONST_PERSISTENT); return SUCCESS; }
留意
给出 C 宏的 PHP 常量值是一个很好的实践。事情变得容易了,这就是我们做的。
按照你的常量类型,你将使用 REGISTER_LONG_CONSTANT()
、 REGISTER_DOUBLE_CONSTANT()
等等。API 和宏位于 Zend/zend_constants.h中。
flag 在CONST_CS
(case-sensitive constant 大小写敏锐常量,我们想要的)和CONST_PERSISTENT
(耐久性常量,在恳求中也是我们想要的)之间是混合的 OR 操纵。
此刻在 C 中的temperature_converter($temp, $type = TEMP_CONVERTER_TO_CELSIUS)
函数:
ZEND_BEGIN_ARG_INFO_EX(arginfo_temperature_converter, 0, 0, 1) ZEND_ARG_INFO(0, temperature) ZEND_ARG_INFO(0, mode) ZEND_END_ARG_INFO();
我们得到了一个必需的参数,两个中的一个。那就是我们声明的。其默许值不是一个参数声明可以解决的,那将在一秒钟内完成。
然后我们将我们的新函数增加到函数注册向量:
static const zend_function_entry pib_functions[] = { PHP_FE(fahrenheit_to_celsius,arginfo_fahrenheit_to_celsius) /* seen above */ PHP_FE(celsius_to_fahrenheit,arginfo_celsius_to_fahrenheit) /* seen above */ PHP_FE(temperature_converter, arginfo_temperature_converter) /* our new function */ }
函数主体:
PHP_FUNCTION(temperature_converter) { double t; zend_long mode = TEMP_CONVERTER_TO_CELSIUS; zend_string *result; if (zend_parse_parameters(ZEND_NUM_ARGS(), "d|l", &t, &mode) == FAILURE) { return; } switch (mode) { case TEMP_CONVERTER_TO_CELSIUS: result = strpprintf(0, "%.2f degrees fahrenheit gives %.2f degrees celsius", t, php_fahrenheit_to_celsius(t)); RETURN_STR(result); case TEMP_CONVERTER_TO_FAHRENHEIT: result = strpprintf(0, "%.2f degrees celsius gives %.2f degrees fahrenheit", t, php_celsius_to_fahrenheit(t)); RETURN_STR(result); default: php_error(E_WARNING, "Invalid mode provided, accepted values are 1 or 2"); } }
记得好好看 README.PARAMETER_PARSING_API。它不是一个很难的 API,你必需熟知它。
我们使用 “d|l” 作为 zend_parse_parameters()
的参数。一个 double、或(管道“|”)、一个 long。留意,假如在运转时不供给可选参数(提示一下,ZEND_NUM_ARGS()
是啥),则 &mode
不会被 zpp() 触及。这就是为什么我们供给了一个TEMP_CONVERTER_TO_CELSIUS
默许值给该变量。
然后我们使用 strpprintf()
去构建一个 zend_string,并且使用 RETURN_STR()
返回它到 return_value
zval。
留意
strpprintf()
和它的伴侣们在打印函数章节有说明过。
使用 Hashtable (PHP 数组)
此刻让我们来玩一下PHP 数组并设计:
function multiple_fahrenheit_to_celsius(array $temperatures) { foreach ($temperatures as $temp) { $return[] = fahreinheit_to_celsius($temp); } return $return; }
所以在 C 说话实现的时候,我们需要zend_parse_parameters()
并恳求一个数组,遍历它,停止数学运算,并将结果作为数组增加到 return_value
:
ZEND_BEGIN_ARG_INFO_EX(arginfo_multiple_fahrenheit_to_celsius, 0, 0, 1) ZEND_ARG_ARRAY_INFO(0, temperatures, 0) ZEND_END_ARG_INFO(); static const zend_function_entry pib_functions[] = { /* ... */ PHP_FE(multiple_fahrenheit_to_celsius, arginfo_multiple_fahrenheit_to_celsius) PHP_FE_END }; PHP_FUNCTION(multiple_fahrenheit_to_celsius) { HashTable *temperatures; zval *data; if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &temperatures) == FAILURE) { return; } if (zend_hash_num_elements(temperatures) == 0) { return; } array_init_size(return_value, zend_hash_num_elements(temperatures)); ZEND_HASH_FOREACH_VAL(temperatures, data) zval dup; ZVAL_COPY_VALUE(&dup, data); convert_to_double(&dup); add_next_index_double(return_value, php_fahrenheit_to_celsius(Z_DVAL(dup))); ZEND_HASH_FOREACH_END(); }
留意
你需要知道 Hashtable 的工作道理,并且必读 zval 章节
在这里,C 说话那部分将更快,由于不需要在 C 轮回中调取 PHP 函数,但是一个静态(大概由编纂器内联的)函数,它的运转速度快了几个数目级,并且运转初级 CPU 指令所需的时间也更少。这并不是说这个小小的演示函数在代码机能方面需要如此多的关注,只要记住为什么我们有时会使用 C 说话代替 PHP。
治理援用
此刻让我们开端玩 PHP 援用。您已经从 zval 章节 理解到援用是在引擎中使用的一种非凡技巧。作为提示,援用(我们指的是&$php_reference
)是分配给 zval
的,储备在 zval
的容器中。
所以,只要记住援用是啥乃至它们的设计目的,就不难将它们处置成 PHP 函数。
假如你的函数接受一个参数作为援用,你必需在参数签名中声明,并从你的 zend_parse_parameter()
调取中传递一个援用。
让我们像平常一样,第一使用 PHP 示例:因此,此刻C中,第一我们必需更换 arg_info
:
ZEND_BEGIN_ARG_INFO_EX(arginfo_fahrenheit_to_celsius, 0, 0, 1) ZEND_ARG_INFO(1, fahrenheit) ZEND_END_ARG_INFO();
" 1 ",中传递的 ZEND_ARG_INFO()
宏告诉引擎必需通过援用传递参数。
然后,当我们接收到参数时,我们使用 z
参数类型,以告诉我们但愿将它作为一个 zval
给出。当我们向引擎提醒它应当向我们传递一个援用这一事实时,我们将获得对该 zval
的援用,也就是它的类型为is_reference
时,我们只需要解援用它(即猎取储备到 zval
中的 zval
),并按原样修改它,由于援用的预测行动是您必需修改援用所携带的值:
PHP_FUNCTION(fahrenheit_to_celsius) { double result; zval *param; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", ¶m) == FAILURE) { return; } ZVAL_DEREF(param); convert_to_double(param); ZVAL_DOUBLE(param, php_fahrenheit_to_celsius(Z_DVAL_P(param))); }
完成。
留意
默许
return_value
值为NULL
。假如我们不碰它,函数将返回PHP的NULL
。