>  기사  >  백엔드 개발  >  작은 지식과 큰 지식을 위한 PHP 기능 등록

작은 지식과 큰 지식을 위한 PHP 기능 등록

coldplay.xixi
coldplay.xixi앞으로
2020-09-03 16:24:442132검색

작은 지식과 큰 지식을 위한 PHP 기능 등록

【관련 학습 추천: php 프로그래밍(동영상)】

PHP 함수 등록 및 사용

PHP 확장의 주요 목표는 사용자를 위해 새로운 PHP 함수를 등록하는 것입니다. , PHP 함수는 매우 복잡하고 Zend 엔진과 밀접하게 관련된 메커니즘을 완전히 이해하기 어렵지만 다행스럽게도 PHP 확장 메커니즘은 이러한 복잡성을 추상화하는 다양한 방법을 제공하기 때문에 이 장에서는 이러한 지식이 필요하지 않습니다.

확장 프로그램에 새로운 PHP 함수를 등록하고 사용하는 것은 간단한 단계이지만 전반적인 상황을 깊이 이해하는 것은 훨씬 더 복잡합니다. zend_function 장의 첫 번째 단계가 도움이 될 수 있습니다.

분명히 유형, 특히 zendValues ​​및 메모리 관리를 숙지해야 합니다.

zend_function_entry 구조체

zend_function과 혼동하지 마세요. struct, zend_function_entry는 엔진에 대한 함수를 등록하기 위해 확장에서 사용됩니다. 여기를 보세요: 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 函数会被注册到 ZEND 引擎当中。一个扩展可以在扩展结构中声明一个函数向量。被扩展声明的函数被称为 核心 函数,与 用户 函数(在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;     /* 函数声明向量 */
        int (*module_startup_func)(INIT_FUNC_ARGS);
        int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    /* ... */
};

您将向函数向量传递一个已声明的函数向量。让我们一起来看一个简单的例子:

/* pib.c 头文件*/
PHP_FUNCTION(fahrenheit_to_celsius)
{

}

static const zend_function_entry pib_functions[] =
{
    PHP_FE(fahrenheit_to_celsius, NULL)
};

zend_module_entry pib_module_entry = {
    STANDARD_MODULE_HEADER,
    "pib",
    pib_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    "0.1",
    STANDARD_MODULE_PROPERTIES
};

我们来试试一个简单的函数 fahrenheit_to_celsius() (名字告诉了我们它的作用)

通过使用 PHP_FUNCTION() 宏来定义一个函数。后者将传递它的参数并扩展成正确的结构。然后,我们把函数符号汇总并将其添加到 pib_functions 向量中。这就是通过 zend_module_entry 符号延伸的 zend_function_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’ 被添加到你的函数名称中,以防止PHP 及其模块在编译中造成符号名称冲突。因此,我们的 fahrenheit_to_celsius() PHP 函数使用了 zif_fahrenheit_to_celsius() 的处理程序。它几乎和每个 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_to_celsius($fahrenheit)

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;
구조가 복잡하지 않다는 것을 알 수 있습니다. 이 모든 것은 새로운 기능을 선언하고 등록하는 데 필요합니다. 자세히 소개하겠습니다: 🎜🎜함수 이름: fname. 추가할 내용은 별로 없습니다. 무엇을 위한 것인지 아시죠? const char * 유형이라는 점에 유의하세요. 이는 엔진에는 적용되지 않습니다. 이 fname은 엔진이 내부 zend_string에서 생성할 모델입니다. 🎜🎜그럼 핸들러를 살펴보겠습니다. 이는 함수의 본문이 될 C 코드에 대한 함수 포인터입니다. 여기서는 매크로를 사용하여 선언을 단순화하겠습니다(잠시 후에 살펴보겠지만). 이 함수를 입력하면 PHP 사용자 영역 함수와 마찬가지로 함수가 수신하는 매개변수를 구문 분석하고 반환 값을 생성할 수 있습니다. 이 반환 값은 핸들러에 매개변수로 전달됩니다. 🎜🎜인론. arg_info 변수는 함수가 수신할 API 인수를 선언하는 것입니다. 다시 말하지만, 이 부분은 깊이 이해하기 어려울 수 있지만 너무 깊게 이해할 필요는 없습니다. 매개변수 선언을 추상화하고 단순화하기 위해 다시 매크로를 사용합니다. 알아야 할 것은 함수를 사용하기 위해 여기에 매개변수를 선언할 필요는 없지만 그렇게 하는 것이 좋습니다. 우리는 여기로 다시 올 것이다. 인수는 arg_info의 배열이므로 해당 크기는 num_args로 전달됩니다. 🎜🎜그럼 플래그하세요. 이 장에서는 이에 대해 자세히 설명하지 않습니다. 이는 내부적으로 사용되며 zend_function 섹션에서 이에 대해 자세히 알아볼 수 있습니다. 🎜🎜PHP 함수 등록🎜🎜확장 기능이 로드되면 PHP 함수가 ZEND 엔진에 등록됩니다. 확장은 확장 구조에서 함수 벡터를 선언할 수 있습니다. 확장에 의해 선언된 함수는 Core 함수라고 하며, User 함수(PHP 사용자가 선언하고 사용하는 함수)와 달리 현재 요청이 끝날 때 실행되지 않습니다. 로그아웃됩니다: 영원히 사용할 수 있습니다. 🎜🎜참고로 다음은 가독성을 위한 PHP 확장 구조의 약어입니다.🎜
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 },
    };
🎜 간단한 함수 fahrenheit_to_celsius()를 사용해 보겠습니다(이름에서 그 기능을 알 수 있습니다). 🎜🎜PHP_FUNCTION() 매크로를 사용하여 함수를 정의합니다. 후자는 인수를 전달하고 올바른 구조로 확장됩니다. 그런 다음 함수 기호를 집계하여 pib_functions 벡터에 추가합니다. 이는 zend_module_entry 기호를 통해 확장된 zend_function_entry * 유형입니다. 이 벡터에서는 PHP_FE 매크로를 통해 PHP 함수를 추가합니다. 후자에는 PHP 함수 이름이 필요하고 NULL 값을 전달하는 경우 인수 벡터가 필요합니다. 🎜🎜php_pib.h 헤더 파일에서 C와 마찬가지로 여기에 함수를 선언해야 합니다. 🎜
/* 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)
🎜보시다시피 함수 선언은 정말 쉽습니다. 홍씨는 우리를 위해 모든 노력을 다해주었다. 다음은 위와 동일한 코드이지만 매크로가 확장되어 작동 방식을 확인할 수 있습니다. 🎜
PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius)
🎜 PHP_FUNCTION()zif_로 끝나는 방식에 유의하세요. 코드 시작 부분>이 확장됩니다. C 기호로. <em>'zif'</em>가 함수 이름에 추가되어 PHP와 해당 모듈이 컴파일 중에 기호 이름 충돌을 일으키는 것을 방지합니다. 따라서 <code>fahrenheit_to_celsius() PHP 함수는 zif_fahrenheit_to_celsius() 핸들러를 사용합니다. 거의 모든 PHP 함수와 같습니다. zif_var_dump로 검색하시면 PHP var_dump() 등의 소스코드 함수를 보실 수 있습니다. 🎜🎜함수 매개변수 선언🎜🎜지금까지 확장 프로그램을 "컴파일"하여 PHP에 로드하면 함수가 렌더링하는 반사 메커니즘을 볼 수 있습니다. 🎜
function fahrenheit_to_celsius($fahrenheit)
{
    return 5/9 * ($fahrenheit - 32);
}
🎜하지만 매개변수가 누락되었습니다. fahrenheit_to_celsius($fahrenheit) 함수 서명을 발행하는 경우 필수 매개변수가 필요합니다. 🎜

你必须了解,函数声明和函数内部的运行无关。这意味着即便没有声明参数,我们现在编写函数也可能会起作用。

注意

声明参数虽然不是强制性的,但是我们强烈推荐使用。反射 API 可通过使用参数获取函数的信息。Zend 引擎也用到参数,尤其是当我们谈及引用传参或者返回引用的函数时。

要声明参数,我们必须要熟悉 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)

这一系列的宏可以让你处理每个用例。
This bunch of macros allow you to deal with every use-case.

  • ZEND_BEGIN_ARG_INFO_EX() 允许你声明你的函数能接收多少个必要参数。它还允许你声明一个 &return_by_ref() 函数。
  • 那么你每个参数都需要 ZEND_ARG_***_INFO() 之一。使用它你可以判断参数是否为 &$passed_by_ref 以及是否需要类型提示。

注意

如果你不知道怎样去命名参数向量符号,则一种做法是使用 ‘arginfo_[function name]’ 模式。

所以回到我们的 fahrenheit_to_celsius() 函数,我们这里申明一个简单的按值返回函数(非常经典的用例),其中一个参数称为 fahrenheit ,且未通过引用传递(又一次的传统用例)。

这就创建了类型 zend_internal_arg_info[] (一个向量, 或一个数组, 都相同) 的 arginfo_fahrenheit_to_celsius 符号,现在我们必须要使用该符号回到函数声明中来添加给它一些参数。

PHP_FE(fahrenheit_to_celsius, arginfo_fahrenheit_to_celsius)

至此我们完成了,现在反射可以看见参数了,并会告知引擎在引用不匹配的情况下该怎么做。太棒了!

注意

还有其他宏。ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX() f.e. 你可以在 Zend/zend_api.h 的源代码中找到所有这些文件。

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


想了解更多编程学习,敬请关注php培训栏目!

위 내용은 작은 지식과 큰 지식을 위한 PHP 기능 등록의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제