百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>网页制作> 小见识大学问的注册 PHP 函数
分享文章到:

小见识大学问的注册 PHP 函数

发布时间:09/01 来源:未知 浏览: 关键词:

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;

真的是一个简便的构造(假如你深入理解常量是怎样治理到引擎中,那大概会是一场噩梦)。你声明了namevalue,一些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", &param) == 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


打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有150人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板