[Traduction] [développement et intégration d'extensions php] Chapitre 5 - Votre première extension

黄舟
Libérer: 2023-03-05 16:14:01
original
1226 Les gens l'ont consulté

Votre première extension

La construction de chaque extension PHP nécessite au moins deux fichiers : un fichier de configuration, qui indique à la compilation quels fichiers construire et quelles bibliothèques externes sont requises, et au moins un fichier source , qui fait le travail réel.

Extensions de profilage

En fait, il y aura généralement un deuxième ou un troisième fichier de configuration, et un ou plusieurs fichiers d'en-tête pour votre extension, dont vous avez besoin. pour ajouter un fichier de chaque type et travailler avec eux.

Fichiers de configuration

Pour commencer, d'abord dans le répertoire ext/ de votre arborescence des sources php Créez un répertoire nommé sample En fait, ce nouveau. Le répertoire peut être placé n'importe où, mais afin de démontrer les options de build win32 et statiques plus tard dans ce chapitre, nous allons d'abord le créer dans le répertoire du code source

Étape suivante, entrez dans ce répertoire, créez un fichier nommé config. .m4, et tapez le contenu suivant :

PHP_ARG_ENABLE(sample,  
  [Whether to enable the "sample" extension],  
  [  enable-sample        Enable "sample" extension support])  
  
if test $PHP_SAMPLE != "no"; then  
  PHP_SUBST(SAMPLE_SHARED_LIBADD)  
  PHP_NEW_EXTENSION(sample, sample.c, $ext_shared)  
fi
Copier après la connexion

Il s'agit de la configuration minimale requise pour pouvoir appeler l'option activate-sample lorsque ./configure Le deuxième paramètre de PHP_ARG_ENABLE sera affiché lors de la configuration étendue. Le fichier est atteint pendant le traitement ./configure. Le troisième paramètre sera affiché sous forme de message d'aide lorsque l'utilisateur du terminal exécute ./configure --help.

Vous êtes-vous déjà demandé pourquoi certaines configurations d'extension utilisent l'activation- extname, alors que certaines extensions utilisent with-extname ? Il n'y a aucune différence fonctionnelle entre les deux. En fait, activate signifie que l'activation de cette fonctionnalité ne nécessite aucune autre bibliothèque tierce, en revanche, with signifie qu'aucune autre bibliothèque tierce. sont nécessaires pour activer cette fonctionnalité. Il existe d'autres conditions préalables pour utiliser cette fonctionnalité

Maintenant, votre exemple d'extension n'a pas besoin d'être lié à d'autres bibliothèques, vous n'avez donc besoin que d'utiliser la version d'activation au chapitre 17 ". Bibliothèques externes", nous présenterons l'utilisation de with et demanderons au compilateur d'utiliser des paramètres CFLAGS et LDFLAGS supplémentaires.

Si l'utilisateur final appelle ./configure avec l'option activate-sample, alors la variable d'environnement locale $PHP_SAMPLE, sera défini sur oui. PHP_SUBST() est une version PHP modifiée de la macro AC_SUBST() standard d'autoconf, qui est requise lors de la création d'extensions en tant que modules partagés.

Enfin, PHP_NEW_EXTENSION() définit le module. et énumère tous les modules qui doivent être utilisés comme fichiers source compilés dans le cadre d'une extension. Si plusieurs fichiers sont requis, ils peuvent être énumérés en utilisant des espaces dans le deuxième argument, par exemple :

PHP_NEW_EXTENSION(sample, sample.c sample2.c sample3.c, $ext_shared)
Copier après la connexion

Le dernier. L'argument correspond à la commande PHP_SUBST(SAMPLE_SHARED_LIBADD), il est également nécessaire lors de la construction d'un module partagé

Fichier d'en-tête

Lors du développement en C, placez la définition du type de données dans un fichier d'en-tête externe. isolez-le et isolez-le du fichier source. L'inclusion est une pratique courante. Bien que PHP ne l'exige pas, elle peut être simplifiée lorsque le module se développe à partir d'un seul fichier source

Dans votre en-tête php_sample.h. fichier, le suivant Le contenu suivant commence

#ifndef PHP_SAMPLE_H  
/* 防止重复包含 */  
#define PHP_SAMPLE_H  
  
/* 定义扩展的属性 */  
#define PHP_SAMPLE_EXTNAME    "sample"  
#define PHP_SAMPLE_EXTVER    "1.0"  
  
/* 在php源码树外面构建时引入配置选项 */  
#ifdef HAVE_CONFIG_H  
#include "config.h"  
#endif  
  
/* 包含php的标准头文件 */  
#include "php.h"  
  
/* 定义入口点符号, Zend在加载这个模块的时候使用 */  
extern zend_module_entry sample_module_entry;  
#define phpext_sample_ptr &sample_module_entry  
  
#endif /* PHP_SAMPLE_H */
Copier après la connexion

Ce fichier d'en-tête accomplit deux tâches principales : Si l'extension est construite à l'aide de l'outil phpize (cette méthode est généralement utilisée dans ce livre), alors HAVE_CONFG_H est défini, donc config. h sera inclus normalement. Quelle que soit la façon dont l'extension est compilée, php.h sera inclus à partir de l'arborescence des sources php <🎜. >

Ensuite, définissez la structure zend_module_entry utilisée par votre extension comme externe, de sorte que lorsque ce module est chargé en utilisant extension=xxx, il puisse être récupéré par Zend en utilisant dlopen et dlsym().

Note de traduction : À propos du module Pour le processus de chargement, veuillez vous référer au blog du traducteur

Le fichier d'en-tête contiendra également un prétraitement, définissant les informations qui seront utilisées dans le fichier d'origine.

Code source

Enfin, la chose la plus importante dont vous avez besoin est sample.c Créez un squelette de code source simple dans :

#include "php_sample.h"  
  
zend_module_entry sample_module_entry = {  
#if ZEND_MODULE_API_NO >= 20010901  
     STANDARD_MODULE_HEADER,  
#endif  
    PHP_SAMPLE_EXTNAME,  
    NULL, /* Functions */  
    NULL, /* MINIT */  
    NULL, /* MSHUTDOWN */  
    NULL, /* RINIT */  
    NULL, /* RSHUTDOWN */  
    NULL, /* MINFO */  
#if ZEND_MODULE_API_NO >= 20010901  
    PHP_SAMPLE_EXTVER,  
#endif  
    STANDARD_MODULE_PROPERTIES  
};  
  
#ifdef COMPILE_DL_SAMPLE  
ZEND_GET_MODULE(sample)  
#endif
Copier après la connexion
C'est aussi simple que cela. Ces trois fichiers sont tout ce dont vous avez besoin pour créer un squelette de module. Cependant, il n'a aucune fonctionnalité. mais comme vous le remplirez plus tard dans cette section, les modèles fonctionnels sont un bon choix. Mais voyons d'abord ce qui se passe. La ligne commençant par

est très simple. Elle contient le fichier d'en-tête que vous venez de créer, et par extension. récupère le reste de l'arborescence source php.

Ensuite, créez la structure zend_module_entry que vous avez définie dans le fichier d'en-tête. Vous devriez remarquer que le premier élément de zend_module_entry est une expression conditionnelle, étant donné le ZEND_MODULE_API_NO actuel. définition. Ce numéro d'API Probablement php4.2.0. Si vous êtes sûr que votre extension ne sera pas installée sur une version plus ancienne que celle-ci, vous pouvez couper la partie #ifdef et inclure directement l'élément STANDARD_MODULE_HEADER. à ce sujet, de toute façon, cela prendra un peu de temps lors de la compilation et n'aura pas d'impact sur le binaire résultant ni sur le temps requis pour le traitement, donc dans la plupart des cas, il est préférable de supprimer directement cette condition. attributs de version suivants.

Les 6 autres éléments sont maintenant initialement définis sur NULL, vous pouvez voir leur objectif dans les commentaires après.

Enfin, en bas, vous pouvez voir tous les php qui peuvent le faire ; être compilé en tant que module partagé Un élément commun à toutes les extensions. Cette courte condition est une référence ajoutée par Zend lorsque vous la chargez dynamiquement. Ne vous inquiétez pas de ses détails, vous devez simplement vous assurer qu'elle existe, sinon la section suivante. peut ne pas fonctionner.

Construire votre première extension

Maintenant que vous avez tous les fichiers, il est temps de compiler et d'installer. Les étapes sont légèrement différentes de la compilation du binaire PHP principal.

在*nix上构建

第一步是使用config.m4中的信息作为末班生成./configure脚本. 这可以运行在你安装主php二进制时附带安装的phpize程序来完成:

$ phpize  
PHP Api Version: 20041225  
Zend Module Api No: 20050617  
Zend Extension Api No: 220050617
Copier après la connexion

Zend Extension Api No前面多出来的2并不是印刷错误; 它对应于Zend引擎2这个版本号, 一般认为要保持这个API编号大于它对应的ZE1版本.

如果你此时查看当前目录 你会注意到比刚才的3个文件多了不少文件. phpize程序结合你扩展的config.m4文件以及从你的php构建中收集的信息和所有让编译发生所需的一切. 这意味着你不用纠缠在makefile和定位php头上面. php已经帮你做了这个工作.

下一步就简单了, 执行./configure. 这里你只要配置你的扩展, 因此你需要做的如下:

$ ./configure --enable-sample
Copier après la connexion

注意这里没有使用enable-debug和enable-maintainer-zts. 这是因为phpize已经将它们的值从主php构建中拿过来并应用到你的扩展的./configure脚本中了.

现在, 构建它! 和其他任何的包一样, 你只需要键入make, 生成的脚本文件就会处理剩下的事情.

构建处理完成后, 你会得到一个消息指出sample.so已经编译并放在了当前构建目录下一个名为"modules"的目录中.

在windows上构建

译者不熟悉windows平台, 因此略过.

将构建的扩展作为共享模块加载

在请求的时候为了让php找到这个模块, 需要将它放到php.ini中extension_dir设置的目录下. 默认的php.ini放在/usr/local/lib/php.ini; 不过这个默认值可能会因为包管理系统而不同. 检查php -i的输出可以看到你这个配置文件在哪里.

如果php.ini中的这个设置没有修改过, 它的值默认是"PHP_HOME/lib/php/extensions/debug-zts-20100525", 后面的debug-zts-20100525分别是是否启用调试, 是否启用zts, PHPAPI编号. 如果你还没有已加载的扩展, 或者说除了sample.so没有其他扩展, 可以将这个值修改到你make产生模块的路径. 否则, 直接将产生的sample.so拷贝到这个设置的目录下.(译注: php -i | grep extension_dir查找你的extension_dir设置)

在extension_dir指向正确的位置后, 有两种方式告诉php去加载你的模块. 第一种是在脚本中使用dl()函数:

<?php  
    dl(&#39;sample.so&#39;);  
    var_dump(get_loaded_modules());  
?>
Copier après la connexion

如果脚本没有显示sample已加载, 则表示有哪里出了问题. 查看输出上面的错误消息作为线索, 或者如果在php.ini中进行了相应的设置就参考error_log.

第二种方式, 也是更常用的方式, 在php.ini中使用extension指令指定要加载的模块. 这个指令在php.ini的设置中是比较特殊的, 可以多次以不同的值使用它. 因此如果你已经在php.ini中设置了一个扩展, 不要在同一行使用分隔符的方式列举, 而是插入新的一行: extension=sample.so. 此时你的php.ini看起来是这样的:

extension_dir=/usr/local/lib/php/modules/  
extension=sample.so
Copier après la connexion

现在你可以不使用dl()运行相同的脚本, 或者直接执行php -m命令, 就可以在已加载模块列表中看到sample了.

所有本章剩余以及以后章节的代码, 都假设你已经按照这里描述的方法加载了当前扩展. 如果你计划使用dl(), 请确认在测试脚本中加入加载的代码(dl()).

静态构建

在已加载模块列表中, 你可能注意到了一些在php.ini中并没有使用extension指令包含的模块. 这些模块是直接构建到php中的, 它们作为主php程序的一部分被编译进php中.

在*nix下静态构建

现在, 如果你现在进入到php源码树的根目录, 运行./configure --help, 你会看到虽然你的扩展和所有其他模块都在ext/目录下, 但它并没有作为一个选项列出. 这是因为, 此刻, ./configure脚本已经生成了, 而你的扩展并不知道. 要重新生成./configure并让它找到你的新扩展, 你需要做的只是执行一条命令:

$ ./buildconf
Copier après la connexion

如果你使用产品发布版的php做开发, 你会发现./buildconf自己不能工作. 这种情况下, 你需要执行: ./buildconf --force来绕过对./configure命令的一些保护.

现在你执行./configure --help就可以看到--enable-sample是一个可用选项了. 此时, 你就可以重新执行./configure, 使用你原来构建主php时使用的所有选项, 外加--enable-sample, 这样构建出来的php二进制文件就是完整的, 包含你自己扩展的程序.

当然, 这样做还有点早. 你的扩展除了占用空间还应该做一些事情.

windows下静态构建

译者不熟悉windows环境, 因此略过.

功能函数

在用户空间和扩展代码之间最快捷的链接就是PHP_FUNCTION(). 首先在你的sample.c文件顶部, #include "php_sample.h"之后增加下面代码:

PHP_FUNCTION(sample_hello_world)  
{  
    php_printf("Hello World!\n");  
}
Copier après la connexion

PHP_FUNCTION()宏函数就像一个普通的C函数定义, 因为它按照下面方式展开:

#define PHP_FUNCTION(name) \  
    void zif_##name(INTERNAL_FUNCTION_PARAMETERS)
Copier après la connexion

这种情况下, 它就等价于:

void zif_sample_hello_world(zval *return_value,  
    char return_value_used, zval *this_ptr TSRMLS_DC)
Copier après la connexion

当然, 只定义函数还不够. 引擎需要知道函数的地址以及应该暴露给用户空间的函数名. 这通过下一个代码块完成, 你需要在PHP_FUNCTION()块后面增加:

static function_entry php_sample_functions[] = {  
    PHP_FE(sample_hello_world,        NULL)  
    { NULL, NULL, NULL }  
};
Copier après la connexion

php_sample_functions向量是一个简单的NULL结束向量, 它会随着你向扩展中增加功能而变大. 每个你要暴露出去的函数都需要在这个向量中给出说明. 展开PHP_FE()宏如下:

{ "sample_hello_world", zif_sample_hello_world, NULL},
Copier après la connexion

这样提供了新函数的名字, 以及指向它的实现函数的指针. 这里第三个参数用于提供暗示信息, 比如某些参数需要引用传值. 在第7章"接受参数"你将看到这个特性.

现在, 你有了一个要暴露的函数列表, 但是仍然没有连接到引擎. 这通过对sample.c的最后一个修改完成, 将你的sample_module_entry结构体中的NULL, /* Functions */一行用php_sample_functions替换(请确保留下那个逗号).

现在, 通过前面介绍的方式重新构建, 使用php命令行的-r选项进行测试, -r允许你不用创建文件直接在命令行运行简单的代码片段:

$ php -r &#39;sample_hello_world();
Copier après la connexion

如果一切OK的话, 你将会看到输出"Hello World!". 恭喜!!!

Zend内部函数

内部函数名前缀"zif_"是"Zend内部函数"的命名标准, 它用来避免可能的符号冲突. 比如, 用户空间的strlen()函数并没有实现为void strlen(INTERNAL_FUNCTION_PARAMETERS), 因为它会和C库的strlen冲突.

zif_的前缀也并不能完全避免名字冲突的问题. 因此, php提供了可以使用任意名字定义内部函数的宏: PHP_NAMED_FUNCTION(); 例如PHP_NAMED_FUNCTION(zif_sample_hello_world)等同于前面使用的PHP_FUNCTION(sample_hello_world)

当使用PHP_NAMED_FUNCTION定义实现时, 在function_entry向量中, 可以对应使用PHP_NAMED_FE()宏. 因此, 如果你定义了自己的函数PHP_NAMED_FUNCTION(purplefunc), 就要使用PHP_NAMED_FE(sample_hello_world, purplefunc, NULL), 而不是使用PHP_FE(sample_hello_world, NULL).

我们可以在ext/standard/file.c中查看fopen()函数的实现, 它实际上使用PHP_NAMED_FUNCTION(php_if_fopen)定义.从用户空间角度来看, 它并不关心函数是什么东西, 只是简单的调用fopen().

函数别名

有时一个函数可能会有不止一个名字. 回想一下, 普通的函数内部定义是用户空间函数名加上zif_前缀, 我们可以看到用PHP_NAMED_FE()宏可以很容易的创建这个可选映射.

PHP_FE(sample_hello_world,    NULL)  
PHP_NAMED_FE(sample_hi,    zif_sample_hello_world,     NULL)
Copier après la connexion

PHP_FE()宏将用户空间函数名sample_hello_world和PHP_FUNCTION(sample_hello_world)展开而来的zif_sample_hello_world关联起来. PHP_NAMED_FE()宏则将用户空间函数名sample_hi和同一个内部实现关联起来.

现在, 假设Zend引擎发生了一个大的变更, 内部函数的前缀从zif_修改为pif_了. 这个时候, 你的扩展就不能工作了, 因为当到达PHP_NAMED_FE()时, 发现zif_sample_hello_world没有定义.

这种情况并不常见, 但非常麻烦, 可以使用PHP_FNAME()宏展开sample_hello_world避免这个问题:

PHP_NAMED_FE(sample_hi, PHP_FNAME(sample_hello_world), NULL)
Copier après la connexion

这种情况下, 即便函数前缀被修改, 扩展的zend_function_entry也会使用宏扩展自动的更新.

现在, 你这样做已经可以工作了, 但是我们已经不需要这样做了. php暴露了另外一个宏, 专门设计用于创建函数别名. 前面的例子可以如下重写:

PHP_FALIAS(sample_hi, sample_hello_world, NULL)
Copier après la connexion

实际上这是官方的创建函数别名的方法, 在php源码树中你时常会看到它.

小结

本章你创建了一个简单的可工作的php扩展, 并学习了在主要平台上构建它需要的步骤. 在以后的章节中, 你将会继续丰满这个扩展, 最终让它包含php的所有特性.

php源码树和它编译/构建在各个平台上依赖的工具经常会发生变化, 如果本章介绍的某些地方不能正常工作, 请参考php.net在线手册的Installation一掌, 查看你使用的版本的特殊需求.

以上就是 [翻译][php扩展开发和嵌入式]第5章-您的第一个扩展的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal