什么是 PHP 扩展?

什么是 PHP 扩展?

深入了解 PHP 扩展和扩展框架

在这里,我们会详细介绍 PHP 扩展是什么样的,以及如何使用一些工具生成框架。这允许我们使用框架代码并且修改它,而不是从头开始手动创建每个需要的部分。

我们也将详细介绍你可以/应该如何组织你的扩展文件,引擎如何加载它们,还有基本上了解有关 PHP 扩展的所有信息。

引擎如何加载扩展

你若记得关于构建 PHP 扩展的章节,便知道如何编译/构建并安装它。

你可以构建静态编译扩展,那、这些是 PHP 的核心并且融入其中。它们不是表示为 .so 的文件,而是表示链接最终的 PHP 可执行(ELF)的 .o 对象。因此,此类扩展不可以禁用,它们是 PHP 可执行主体代码的一部分:无论你如何说和做, 它们都在这里面。某些扩展要求静态构建,即,ext/core,ext/standard,ext/spl 和 ext/mysqlnd (非详尽列表)。

你可以通过在 main/internal_functions.c 查找静态编译扩展的列表,该文件是在编译 PHP 时生成的。此步骤在构建 PHP 章节中详细介绍。

然后,你也可以构建动态加载扩展。那些是著名的 extension.so 文件是在单个编译过程的最后产生的。动态加载扩展具有在运行时可插拔的优点,并且不需要重新编译所有 PHP 即可禁用或启用。缺点是当它必须加载 .so 文件时,PHP 进程启动时间更长。但是这只是几毫秒,你不会感到困扰。

动态加载扩展的另一个缺点是扩展加载顺序。某些扩展可能需要先加载其他扩展。尽管这不是一个好的习惯,我们也可以看到 PHP 扩展系统允许你声明依赖来执行这样的顺序,但是依赖通常是一个坏主意,应该避免。

最后:PHP 静态编译扩展先于动态编译扩展。意味着它们的 MINIT() 在 extensions.so 文件的 MINIT() 之前被调用。

当 PHP 启动,它很快去解析其不同的 INI 文件。如果有的话,在之后可使用 “extension=some_ext.so” 声明要加载的扩展。然后PHP 收集从 INI 配置解析出的每个扩展,并且尝试以同样添加在 INI 文件的顺序加载它们,直到某些扩展声明了某些依赖(依赖将在它之前加载)。

注意

如果你使用操作系统软件包管理器,你可能注意到,软件包通常使用标题编号(即 00_ext.ini,01_ext.ini 等等)来命名其扩展。这是为了掌握将要加载的顺序扩展。某些不常见的扩展要求运行特殊的顺序。我们想要提醒你,先加载依赖的其他扩展是个坏方法。

为了加载扩展,使用 libdl 和它的 dlopen()/dlsym() 函数。

查找的符号是 get_module() 符号,这意味着你的扩展必须导出才能加载。这很常见,就像你使用框架脚本(我们能很快预见),然后使用 ZEND_GET_MODULE(your_ext) 宏生成代码,像这样:

#define ZEND_GET_MODULE(name)

BEGIN_EXTERN_C()

ZEND_DLEXPORT zend_module_entry *get_module(void) { return &name##_module_entry; }

END_EXTERN_C()

如你所见,该宏使用时声明了一个全局符号:get_module() 函数,一旦加载扩展,将通过引擎调用该函数。

注意

PHP 用于加载扩展的源代码位于 ext/standard/dl.c。

什么是 PHP 扩展?

PHP 扩展,不要和 Zend extension 混淆,它是通过使用 zend_module_entry 结构来设置的:

struct _zend_module_entry {

unsigned short size; /*

unsigned int zend_api; * STANDARD_MODULE_HEADER

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); *

int (*request_startup_func)(INIT_FUNC_ARGS); * 生命周期函数(钩子)

int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); *

void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); */

const char *version; /* 模块版本 */

size_t globals_size; /*

#ifdef ZTS *

ts_rsrc_id* globals_id_ptr; *

#else * Globals management

void* globals_ptr; *

#endif *

void (*globals_ctor)(void *global); *

void (*globals_dtor)(void *global); */

int (*post_deactivate_func)(void); /* 很少使用的生命周期钩子 */

int module_started; /* 是否已启动模块(内部使用) */

unsigned char type; /* 模块类型(内部使用) */

void *handle; /* dlopen() 返回句柄 */

int module_number; /* 模块号 */

const char *build_id; /* 构建编号, STANDARD_MODULE_PROPERTIES_EX 的一部分*/

};

前四个参数已经在构建扩展章节解释过了。它们通常使用STANDARD_MODULE_HEADER宏来填充。

ini_entry 向量实际上未使用。你可以使用特殊宏注册 INI 条目。

然后你可以声明依赖关系,这意味着你的扩展可能需要先加载另一个扩展,或者声明与另一个扩展的冲突。使用 deps 字段可以完成。事实上,这是非常常见的用法,更普遍的做法是,通过 PHP 扩展创建依赖,这是个坏习惯。

之后,你声明一个 name。不用说,这是你的扩展名(可以不同于它的 .so 文件)。在大多数操作下,注意大小写敏感,我们建议你使用缩写,小写,无空格(使操作更容易)。

然后是 functions 字段。它是扩展想要注册到引擎的某些 PHP 函数的指针。我们将在专门章节讨论。

接下来是5个生命周期钩子。查看它们的专门章节。

你的扩展可以使用 version 字段将版本号发布为 char *。该字段作为扩展信息的一部分读取,即通过 phpinfo() 或者像 ReflectionExtension::getVersion()的反射 API读取。

接下来,我们将看到很多关于全局变量的字段。全局管理有专门章节介绍。

最后,结尾字段通常是STANDARD_MODULE_PROPERTIES宏的一部分,不用你手动去操作它们。引擎会为你提供一个module_number进行内部管理,并且扩展类型将会设置到 MODULE_PERSISTENT。就像你的扩展使用 PHP 的用户区 dl() 函数加载一样,它可以是 MODULE_TEMPORARY,但是该用例很少见的,不适用每个 SAPI,并且临时扩展通常会给引擎带来许多问题。

使用脚本生成扩展框架

现在,我们来看怎么生成一个扩展的框架,以便你可以以最少的内容和结构开始一个新的扩展,而不会被迫从头开始自己创建。

框架生成脚本位于 php-src/ext/ext_skel,并且将其用作模板的结构存放在 php-src/ext/skeleton。

注意

随着 PHP 的发展,脚本和结构移也有一些变化。

你可以分析那些脚本是如何工作的,但是基本的用法是:

> cd /tmp

/tmp> /path/to/php/ext/ext_skel --skel=/path/to/php/ext/skeleton --extname=pib

[ ... generating ... ]

/tmp> tree pib/

pib/

├── config.m4

├── config.w32

├── CREDITS

├── EXPERIMENTAL

├── php_pib.h

├── pib.c

├── pib.php

└── tests

└── 001.phpt

/tmp>

你可以看见一个非常基本的、最小的结构生成了。 你已经学习过构建扩展章节,扩展的待编译文件一定要声明为 config.m4 。该框架只生成 .c 文件。例如,我们将扩展名为 “pib”,因此得到一个 pib.c 文件,并且我们必须取消 config.m4 中的 –enable-pib 注释,让它能被编译。

每个 C 文件通常都会附带头文件。这里的结构是 php_.h,所以对我们来说就是 php_pib.h。不要更改它的名字,构建系统希望头文件有这样的命名约定。

你可以看见一个最小的测试结构生成了。

让我们打开 pib.c。在这里,所有内容都被注释掉了,所以我们不必写太多行。

基本上,我们可以看到引擎加载我们的扩展所需的的模块符号发布在这里:

#ifdef COMPILE_DL_PIB

#ifdef ZTS

ZEND_TSRMLS_CACHE_DEFINE()

#endif

ZEND_GET_MODULE(pib)

#endif

如果你通过了配置脚本的 –enable- 标志,则定义了 COMPILE_DL_ 宏。我们也看到在 ZTS 模式的情况下,TSRM 本地存储指针定义为 ZEND_TSRMLS_CACHE_DEFINE() 宏的一部分。

之后,没有什么好说的,因为所有的内容都注释了,对你来说应该很清楚。

扩展框架生成器的新时代

自从此提交以来,扩展框架生成器有了新的风格:

它现在可以运行在 Windows 而不需要 Cygwin 和其他没意义的东西。它不再包含生成 XML 文档的方法(PHP 文档程序已经在 phpdoc/doc-base 下的 svn 获得用于该文档的工具),并且它不再支持函数桩。

这里是有效的选项:

php ext_skel.php --ext [--experimental] [--author ]

[--dir ] [--std] [--onlyunix]

[--onlywindows] [--help]

--ext The name of the extension defined as

--experimental Passed if this extension is experimental, this creates

the EXPERIMENTAL file in the root of the extension

--author Your name, this is used if --header is passed and

for the CREDITS file

--dir Path to the directory for where extension should be

created. Defaults to the directory of where this script

lives

--std If passed, the standard header and vim rules footer used

in extensions that is included in the core, will be used

--onlyunix Only generate configure scripts for Unix

--onlywindows Only generate configure scripts for Windows

--help This help

新的框架生成器将生成具有固定三个功能的框架,你可以定义其他函数,并且将具体的主体改成你想要的。

注意

记住新的 ext_skel 不再支持原型文件。

发布 API

如果我们打开头文件,我们可以看到:

#ifdef PHP_WIN32

# define PHP_PIB_API __declspec(dllexport)

#elif defined(__GNUC__) && __GNUC__ >= 4

# define PHP_PIB_API __attribute__ ((visibility("default")))

#else

# define PHP_PIB_API

#endif

这些定义了名为 PHP__API的宏(对我们来说是 PHP_PIB_API),并解析为 GCC 自定义属性可见性(“默认”)。

在 C 语言,你可以告诉链接器从最终对象中隐藏每个符号。这是用 PHP 做的,对每个符号,不止是静态符号(根据定义,这些符号均未发布)。

警告

默认 PHP 编译行告诉我们编译器隐藏了每个符号而不导出它们。

然后,你想要你的扩展发布给其他扩展或其他部分最终 ELF 文件使用的话,你应该“不隐藏”符号。

注意

记住,你可以在 Unix 下使用 nm 阅读 ELF 已发布和隐藏符号。

我们无法深入解释这些概念,也许下面的链接可以帮助你?

gcc.gnu.org/wiki/Visibility

www.iecc.com/linker/linker10.html

www.akkadia.org/drepper/dsohowto.p...

www.faqs.org/docs/Linux-HOWTO/Progr...

developer.apple.com/library/conten...

基本上,如果你想要你的 C 符号对其他扩展公开有效,你应该使用特殊的 PHP_PIB_API 宏声明。传统的用例是发布类符号(zend_class_entry* 类型),以便其他扩展可以挂载你的已发布类,并替换它们的一些句柄。

注意

请注意,这仅在传统 PHP 有效。如果你使用 Linux 发行版的 PHP,这些补丁是在加载时为了解析符号,而不是懒惰符号,因此不起作用。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接

我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/php-internals/p...

译文地址:https://learnku.com/docs/php-internals/p...

相关推荐

喇叭花的五种颜色及特点(探究喇叭花五种不同的颜色和它们各自的独特魅力)
到底什么是hash呢?hash碰撞?为什么HashMap的初始容量是16?
400米怎么跑?掌握跑步策略和技巧,成绩能提高10秒