注册和使用 PHP 函数
PHP 扩展的主要目标是为用户空间注册新的 PHP 函数。PHP 函数非常复杂,很难完全理解其与 Zend 引擎深度挂钩的机制,但幸运的是,我们不需要这方面的知识,因为 PHP 扩展机制提供了许多方法来抽象出如此复杂的内容。
在扩展中注册和使用新的 PHP 函数是一个简单的步骤。然而,深入了解大局则相当复杂。第一步是阅读 zend_function 章节,这可能会有所帮助。
显然,您需要掌握类型,尤其是 zval 和内存管理。此外,还要了解您的钩子。
zend_function_entry 结构
不要与 zend_function 结构混淆,zend_function_entry
用于在扩展中向引擎注册函数。它如下:
#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value
typedef struct _zend_function_entry {
const char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
const struct _zend_internal_arg_info *arg_info;
uint32_t num_args;
uint32_t flags;
} zend_function_entry;
您可以发现这个结构并不复杂。这就是您声明和注册新函数所需的全部内容。让我们一起详细说明:
函数有一个名字:fname
。没什么好补充的,你知道它的用途了吧?只是注意到了 const char *
类型。这在引擎中是不适用的。这个 fname
是一个模型,引擎会从中创建一个内部的 zend_string
。
然后是 handler
。这是一个指向将成为该函数主体的 C 代码的函数指针。在这里,我们将使用宏来简化其声明(我们稍后会看到)。在这个函数中,我们将能够解析函数接收的参数,并像任何 PHP 用户空间函数一样生成返回值。请注意,此返回值作为参数传递给我们的处理程序。
参数。arg_info
变量用于声明我们的函数将接受的 API 参数。同样,这部分可能很难深入理解,但我们不需要太深入,我们将再次使用宏来抽象和简化参数声明。您应该知道的是,您不需要在这里声明任何参数才能使函数工作,但强烈建议这样做。我们稍后会回到这一点。参数是 arg_info
的数组,因此其大小作为 num_args
传递。
然后是 flags
。我们不会在本章中详细介绍标志。它们是内部使用的,您可以在专门的 zend_function
章节中找到一些详细信息。
注册 PHP 函数
PHP 函数会在加载扩展时注册到引擎中。扩展可以在扩展结构中声明一个函数向量。扩展声明的函数被称为 “内部” 函数,与 “用户” 函数(使用 PHP 用户域声明和使用的函数)相反,它们不会在当前请求结束时取消注册:它们是永久性的。
为了便于阅读,下面是 PHP 扩展结构的缩写:
struct _zend_module_entry {
unsigned short size;
unsigned int zend_api;
unsigned char zend_debug;
unsigned char zts;
const struct _zend_ini_entry *ini_entry;
const struct _zend_module_dep *deps;
const char *name;
const struct _zend_function_entry *functions; /* function declaration vector */
int (*module_startup_func)(INIT_FUNC_ARGS);
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
/* ... */
};
您将向 functions
向量传递一个已声明的函数向量。让我们来看一个简单的例子:
/* pib.c */
PHP_FUNCTION(fahrenheit_to_celsius) // 声明函数
{
}
static const zend_function_entry pib_functions[] =
{
PHP_FE(fahrenheit_to_celsius, NULL) // 使用函数,无参数声明
PHP_FE_END
};
zend_module_entry pib_module_entry = {
STANDARD_MODULE_HEADER,
"pib",
pib_functions, // 函数数组
NULL,
NULL,
NULL,
NULL,
NULL,
"0.1",
STANDARD_MODULE_PROPERTIES
};
让我们用一个简单的 fahrenheit_too_celsius()
函数来玩玩(这个函数的名字告诉了我们它将执行什么)。
使用 PHP_FUNCTION()
宏可以定义一个函数。后者将接收其参数并展开为正确的结构。然后,我们收集函数符号并将其添加到 pib_functions
向量中。这个向量的类型是 zend_function_entry *
,也就是我们的 zend_module_entry
符号所保护的类型。在这个向量中,我们使用 PHP_FE
宏添加 PHP 函数。后者需要 PHP 函数名和一个参数向量,我们暂时将其传递为 NULL
。
在 php_pib.h
头文件中,我们应该像 C 语言那样声明我们的函数:
/* pib.h */
PHP_FUNCTION(fahrenheit_to_celsius);
正如您所看到的,声明函数非常简单。宏为我们完成了所有艰巨的工作。下面是相同的代码,但宏已经展开,这样你就可以看看它们的工作了:
/* pib.c */
void zif_fahrenheit_to_celsius(zend_execute_data *execute_data, zval *return_value)
{
}
static const zend_function_entry pib_functions[] =
{
{ "fahrenheit_to_celsius", zif_fahrenheit_to_celsius, ((void *)0),
(uint32_t) (sizeof(((void *)0))/sizeof(struct _zend_internal_arg_info)-1), 0 },
}
请注意 PHP_FUNCTION()
是如何扩展为以 zif_
开头的 C 符号的。"zif" 代表 Zend 内部函数(Zend Internal Function),添加到函数名称中是为了防止在编译 PHP 及其模块时出现符号名冲突。因此,我们的 fahrenheit_too_celsius()
PHP 函数使用了名为 zif_fahrenheit_too_celsius()
的 C 处理程序。几乎所有的 PHP 函数都是如此。如果查找 “zif_var_dump”,就会看到 PHP var_dump()
源代码函数,等等…
声明函数参数
到目前为止一切顺利,如果编译扩展并将其加载到 PHP 中,就可以通过反射看到函数的存在:
> ~/php/bin/php -dextension=pib.so --re pib
Extension [ <persistent> extension #37 pib version 0.1 ] {
- Functions {
Function [ <internal:pib> function fahrenheit_to_celsius ] {
}
}
但缺少参数。如果我们要发布一个 fahrenheit_too_celsius($fahrenheit)
函数签名,我们需要一个强制参数。
你必须知道的是,参数声明与函数内部工作无关。这意味着,如果我们现在就编写函数体,这个函数就可以正常工作。即使没有声明参数。
声明参数不是必须的,但强烈建议。参数被反射 API 用于获取函数的相关信息。引擎也会使用参数,尤其是当我们讨论通过引用传递参数或返回引用的函数时。 |
要声明参数,我们需要熟悉 zend_internal_arg_info
结构:
typedef struct _zend_internal_arg_info {
const char *name;
const char *class_name;
zend_uchar type_hint;
zend_uchar pass_by_reference;
zend_bool allow_null;
zend_bool is_variadic;
} zend_internal_arg_info;
无需详细说明每个字段,但对参数的理解要比这一单独结构复杂得多。幸运的是,我们再次为您提供了一些宏,为您抽象出这项艰巨的工作:
ZEND_BEGIN_ARG_INFO_EX(arginfo_fahrenheit_to_celsius, 0, 0, 1)
ZEND_ARG_INFO(0, fahrenheit)
ZEND_END_ARG_INFO()
上面的代码详细介绍了如何创建参数,但当我们扩展宏时,会感到有些困难:
static const zend_internal_arg_info arginfo_fahrenheit_to_celsius[] = { \
{ (const char*)(zend_uintptr_t)(1), ((void *)0), 0, 0, 0, 0 },
{ "fahrenheit", ((void *)0), 0, 0, 0, 0 },
};
我们可以看到,宏创建了一个 zend_internal_arg_info
结构。如果你阅读了这些宏的 API,就会明白一切:
/* API only */
#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
#define ZEND_ARG_INFO(pass_by_ref, name)
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null)
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null)
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null)
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name)
这组宏允许你处理每一种使用情况。
-
通过
ZEND_BEGIN_ARG_INFO_EX()
,您可以声明您的函数需要接受多少个参数。它还允许声明一个&return_by_ref()
函数。 -
然后,每个参数都需要一个
ZEND_ARG_***_INFO()
。通过它,您可以判断参数是否为&$passed_by_ref
,以及是否需要类型提示。
如果不知道如何命名参数向量符号,可以使用 “arginfo_[函数名]” 模式。 |
回到我们的 fahrenheit_to_celsius()
函数,我们声明了一个简单的按值返回函数(非常经典的用例),只有一个名为 fahrenheit
的参数,不通过引用传递(这里也是非常传统的)。
这就创建了 arginfo_fahrenheit_to_celsius
符号,其类型为 zend_internal_arg_info[]
(一个向量或数组,两者相同),现在我们必须将其用于函数声明中,为其附加一些参数:
PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius)
这样就完成了,现在反射看到了参数,引擎也知道了在引用不匹配的情况下该怎么做。好极了!
还有其他宏。例如, |
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
声明的函数,在 C 语言中不能返回任何东西。但我们注意到,我们收到了一个名为 return_value
的参数,其类型为 zval *
,这似乎很不错。在用 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()
是一个为你在 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” 表示 “如果需要,我希望将接收到的第一个参数转换为浮点数(double)”。
然后,在该字符串后传递满足第二个参数所需的尽可能多的 C 实数参数。一个 “d” 表示 “一个 double”,然后你现在传递一个 double
的地址,引擎就会填充它的值。
您总是传递一个指针,指向您希望填充的数据。 |
您可以在 PHP 源代码中的 README.PARAMETER_PARSING_API 文件中找到关于 zpp() 字符串格式的最新帮助。请仔细阅读,因为这一步可能会把事情弄糟并导致崩溃。务必检查参数,根据所提供的格式字符串传递与预期相同数量和类型的参数变量。要符合逻辑。
请注意参数解析的正常程序。函数 zend_parse_parameters()
成功时应返回 SUCCESS
,失败时应返回 FAILURE
。失败可能意味着你没有使用 ZEND_NUM_ARGS()
值,而是手动提供了一个值(坏主意),或者你在参数解析时做错了什么。如果是这种情况,那么就应该返回,中止当前函数(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));
}
对您来说,返回值应该很容易,因为您知道 zvals 是如何工作的。您必须填写 return_value
。
为此,需要使用 RETURN_*()
宏和 RETVAL_()
宏。这两种宏都只是设置 return_value
zval 的类型和值,但 RETURN_()
宏会在其后使用 C return 从当前函数返回。
另外,API 还提供了一组宏来处理和解析参数。如果使用 python 风格的指定符,会更易读。
您需要使用以下宏来开始和结束函数参数解析:
ZEND_PARSE_PARAMETERS_START(min_argument_count, max_argument_count) /* takes two parameters */
/* here we will go with argument lists */
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
};
现在是一个更复杂的用例,我们先用 PHP 演示,然后再将其作为 C 语言扩展来实现:
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;
}
}
这个例子有助于我们介绍常量(constants)。
常量在扩展中很容易管理,就像在用户界面中一样。常量是持久的,这通常意味着它们的值应该在不同的请求中持久存在。如果你了解 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;
}
给 PHP 常量赋予 C 宏的值是一种很好的做法。我们就是这么做的。 |
根据你的常量类型,你将使用 REGISTER_LONG_CONSTANT()
、REGISTER_DOUBLE_CONSTANT()
,等等……API 和宏位于 Zend/zend_constants.h
。
标志是 CONST_CS
(区分大小写的常量,我们想要的)和 CONST_PERSISTENT
(跨请求的持久常量,我们也想要)之间的混合 OR 运算。
现在我们 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()
的参数。一个双参数和一个可选参数(管道 “|”)。注意,如果运行时没有提供可选参数(ZEND_NUM_ARGS()
会提醒我们),zpp() 将不会触及 &mode
变量。这就是我们为该变量提供 TEMP_CONVERTER_TO_CELSIUS
默认值的原因。
然后,我们使用 strpprintf()
创建一个 zend_string
,并使用 RETURN_STR()
将其返回到 return_value
zval 中。
|
使用 Hashtables(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();
}
您需要了解 Hashtables 的工作原理,以及必读的 zval 章节。 |
在这里,C 部分会更快,因为您不会在 C 代码的循环中调用 PHP 函数,而是调用静态(可能由编译器内联)C 函数,该函数的速度要快几个数量级,并且运行时所需的低级 CPU 指令要少得多。这并不是因为这个小演示函数需要如此多的代码性能,而只是为了记住我们有时使用 C 语言而不是 PHP 的一个原因。
管理引用
现在让我们开始玩 PHP 引用。您从 zval 章节中了解到,引用是引擎中使用的一种特殊技巧。提醒一下,引用(我们指的是 &$php_reference
)是存储在 zval
容器中的堆分配的 zval
。哈哈。
因此,只要您记住引用是什么以及它们的设计用途,将它们处理到 PHP 函数中并不难。
如果您的函数接受参数作为引用,则必须在参数签名中声明该参数,并从 zend_parse_parameter()
调用中传递引用。让我们像往常一样先看一个 PHP 示例:
function fahrenheit_to_celsius_by_ref(&$fahreinheit)
{
$fahreinheit = 9/5 * $fahrenheit + 32;
}
所以现在在 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)));
}
|